From ae4caac0a98783f1ab7211480a165953b3ed5242 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 28 Oct 2026 22:32:11 +0000 Subject: [PATCH 1/4] Update stacklok/toolhive to v0.59.0 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/upstream-projects.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/upstream-projects.yaml b/.github/upstream-projects.yaml index 83da434e..48eab3ec 100644 --- a/.github/upstream-projects.yaml +++ b/.github/upstream-projects.yaml @@ -64,7 +64,7 @@ projects: - id: toolhive repo: stacklok/toolhive - version: v0.58.0 + version: v0.59.0 # toolhive is a monorepo covering the CLI, the Kubernetes # operator, and the vMCP gateway. It also introduces cross- # cutting features that land in concepts/, integrations/, From ddef5f72e07e5d28f8ec675b27fb0fb3920fba13 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 28 Oct 2026 22:33:33 +0000 Subject: [PATCH 2/4] Refresh reference assets for toolhive v0.59.0 --- docs/toolhive/reference/cli/thv_logs.md | 6 +- docs/toolhive/reference/cli/thv_rm.md | 4 +- docs/toolhive/reference/cli/thv_run.md | 4 +- docs/toolhive/reference/cli/thv_stop.md | 4 +- .../toolhive/reference/helm/operator-crds.mdx | 6 +- docs/toolhive/reference/helm/operator.mdx | 83 ++++++++++++++++--- static/api-specs/toolhive-api.yaml | 65 +++++++++++++-- .../mcpexternalauthconfigs.schema.json | 22 +++++ .../virtualmcpservers.schema.json | 22 +++++ 9 files changed, 187 insertions(+), 29 deletions(-) diff --git a/docs/toolhive/reference/cli/thv_logs.md b/docs/toolhive/reference/cli/thv_logs.md index e3503bb2..2dbeecb8 100644 --- a/docs/toolhive/reference/cli/thv_logs.md +++ b/docs/toolhive/reference/cli/thv_logs.md @@ -22,13 +22,13 @@ Use --proxy to view the logs from the ToolHive proxy process instead. Examples: # View logs of an MCP server - thv logs filesystem + thv logs fetch # Follow logs in real-time - thv logs filesystem --follow + thv logs fetch --follow # View proxy logs instead of container logs - thv logs filesystem --proxy + thv logs fetch --proxy # Clean up old log files thv logs prune diff --git a/docs/toolhive/reference/cli/thv_rm.md b/docs/toolhive/reference/cli/thv_rm.md index 3092f0ef..901b2eab 100644 --- a/docs/toolhive/reference/cli/thv_rm.md +++ b/docs/toolhive/reference/cli/thv_rm.md @@ -18,10 +18,10 @@ Remove one or more MCP servers Remove one or more MCP servers managed by ToolHive. Examples: # Remove a single MCP server - thv rm filesystem + thv rm fetch # Remove multiple MCP servers - thv rm filesystem github slack + thv rm fetch github slack # Remove all workloads thv rm --all diff --git a/docs/toolhive/reference/cli/thv_run.md b/docs/toolhive/reference/cli/thv_run.md index 5d6838a5..15ce0a91 100644 --- a/docs/toolhive/reference/cli/thv_run.md +++ b/docs/toolhive/reference/cli/thv_run.md @@ -82,7 +82,7 @@ The --network flag accepts any Docker-compatible network mode. Examples: # Run a server from the registry - thv run filesystem + thv run fetch # Run a server with custom arguments and toolsets thv run github -- --toolsets repos @@ -97,7 +97,7 @@ Examples: thv run npx://@modelcontextprotocol/server-everything # Run a server in a specific group - thv run filesystem --group production + thv run fetch --group production # Run a remote GitHub MCP server with authentication thv run github-remote --remote-auth \ diff --git a/docs/toolhive/reference/cli/thv_stop.md b/docs/toolhive/reference/cli/thv_stop.md index 12917fb7..83a59042 100644 --- a/docs/toolhive/reference/cli/thv_stop.md +++ b/docs/toolhive/reference/cli/thv_stop.md @@ -17,10 +17,10 @@ Stop one or more MCP servers Stop one or more running MCP servers managed by ToolHive. Examples: # Stop a single MCP server - thv stop filesystem + thv stop fetch # Stop multiple MCP servers - thv stop filesystem github slack + thv stop fetch github slack # Stop all running MCP servers thv stop --all diff --git a/docs/toolhive/reference/helm/operator-crds.mdx b/docs/toolhive/reference/helm/operator-crds.mdx index 3de8b21c..7309e1e0 100644 --- a/docs/toolhive/reference/helm/operator-crds.mdx +++ b/docs/toolhive/reference/helm/operator-crds.mdx @@ -11,9 +11,9 @@ Control CRD installation and preservation with these Helm values. This reference lists values declared in the chart's default `values.yaml`. Templates can also accept optional settings and inherited `global` values. -Chart: `toolhive-operator-crds`. Chart version: `0.58.0`. Source: -[`values.yaml`](https://github.com/stacklok/toolhive/blob/v0.58.0/deploy/charts/operator-crds/values.yaml) -at `v0.58.0`. +Chart: `toolhive-operator-crds`. Chart version: `0.59.0`. Source: +[`values.yaml`](https://github.com/stacklok/toolhive/blob/v0.59.0/deploy/charts/operator-crds/values.yaml) +at `v0.59.0`. ## Values diff --git a/docs/toolhive/reference/helm/operator.mdx b/docs/toolhive/reference/helm/operator.mdx index 447802e3..7394c967 100644 --- a/docs/toolhive/reference/helm/operator.mdx +++ b/docs/toolhive/reference/helm/operator.mdx @@ -10,9 +10,9 @@ Configure the ToolHive operator with these Helm values. This reference lists values declared in the chart's default `values.yaml`. Templates can also accept optional settings and inherited `global` values. -Chart: `toolhive-operator`. Chart version: `0.58.0`. Source: -[`values.yaml`](https://github.com/stacklok/toolhive/blob/v0.58.0/deploy/charts/operator/values.yaml) -at `v0.58.0`. +Chart: `toolhive-operator`. Chart version: `0.59.0`. Source: +[`values.yaml`](https://github.com/stacklok/toolhive/blob/v0.59.0/deploy/charts/operator/values.yaml) +at `v0.59.0`. ## Values @@ -61,6 +61,11 @@ defaultRedis: addr: '' existingSecret: '' existingSecretKey: '' + tls: + caCertSecretRef: + key: ca.crt + name: '' + enabled: false env: [] features: experimental: false @@ -68,7 +73,7 @@ features: gc: gogc: 75 gomemlimit: 110MiB -image: ghcr.io/stacklok/toolhive/operator:v0.58.0 +image: ghcr.io/stacklok/toolhive/operator:v0.59.0 imageDiscovery: enabled: false resources: {} @@ -153,8 +158,8 @@ serviceAccount: labels: {} name: toolhive-operator tolerations: [] -toolhiveRunnerImage: ghcr.io/stacklok/toolhive/proxyrunner:v0.58.0 -vmcpImage: ghcr.io/stacklok/toolhive/vmcp:v0.58.0 +toolhiveRunnerImage: ghcr.io/stacklok/toolhive/proxyrunner:v0.59.0 +vmcpImage: ghcr.io/stacklok/toolhive/vmcp:v0.59.0 volumeMounts: [] volumes: [] ``` @@ -217,7 +222,21 @@ names. Secrets must exist in each workload's namespace. **Type:** `object` -**Default:** `{"addr":"","existingSecret":"","existingSecretKey":""}` +
+Default value + +```yaml +addr: '' +existingSecret: '' +existingSecretKey: '' +tls: + caCertSecretRef: + key: ca.crt + name: '' + enabled: false +``` + +
Default Redis or Valkey session storage settings for workloads without `spec.sessionStorage` configured. @@ -251,11 +270,55 @@ namespace. Used only when a Redis address is configured. Key containing the password in `existingSecret`. When empty, uses `global.redis.existingSecretKey`, then `redis-password`. +### `operator.defaultRedis.tls` + +**Type:** `object` + +**Default:** `{"caCertSecretRef":{"key":"ca.crt","name":""},"enabled":false}` + +TLS settings for the default Redis or Valkey connection. TLS is enabled only +when explicitly requested and a Redis address is configured. + +### `operator.defaultRedis.tls.enabled` + +**Type:** `bool` + +**Default:** `false` + +Verify the Redis server certificate using system roots or the optional CA +certificate below. Requires a configured Redis address. + +### `operator.defaultRedis.tls.caCertSecretRef` + +**Type:** `object` + +**Default:** `{"key":"ca.crt","name":""}` + +Optional CA certificate for verifying the Redis server. The Secret must exist in +each workload's namespace, not the operator namespace. + +### `operator.defaultRedis.tls.caCertSecretRef.name` + +**Type:** `string` + +**Default:** `""` + +Name of an existing Secret containing a PEM-encoded CA certificate. Leave empty +to use system roots. + +### `operator.defaultRedis.tls.caCertSecretRef.key` + +**Type:** `string` + +**Default:** `"ca.crt"` + +Key containing the PEM-encoded CA certificate in the Secret. + ### `operator.image` **Type:** `string` -**Default:** `"ghcr.io/stacklok/toolhive/operator:v0.58.0"` +**Default:** `"ghcr.io/stacklok/toolhive/operator:v0.59.0"` Container image for the operator. @@ -271,7 +334,7 @@ Image pull policy for the operator container and image-discovery containers. **Type:** `string` -**Default:** `"ghcr.io/stacklok/toolhive/proxyrunner:v0.58.0"` +**Default:** `"ghcr.io/stacklok/toolhive/proxyrunner:v0.59.0"` Container image for ToolHive proxy runners created by the operator. @@ -279,7 +342,7 @@ Container image for ToolHive proxy runners created by the operator. **Type:** `string` -**Default:** `"ghcr.io/stacklok/toolhive/vmcp:v0.58.0"` +**Default:** `"ghcr.io/stacklok/toolhive/vmcp:v0.59.0"` Container image for Virtual MCP Server (vMCP) deployments created by the operator. diff --git a/static/api-specs/toolhive-api.yaml b/static/api-specs/toolhive-api.yaml index 7f91f452..07b01ce6 100644 --- a/static/api-specs/toolhive-api.yaml +++ b/static/api-specs/toolhive-api.yaml @@ -220,6 +220,19 @@ components: fails, fails startup. "none" skips the check; the registration is then retired only through RotationKey. type: string + registration_probe_interval: + description: |- + RegistrationProbeInterval, when set, repeats the start-time resolution + while the server runs, as a Go duration of at least 1m (e.g. "5m"): the + cached registration is re-read and checked with RegistrationProbe, a + rejected one is replaced, a missing one is registered, and when the + result differs from the client the server holds (also when another + replica replaced the row) the server switches to it without a restart. + Each check sends at most one RegistrationProbe request to the upstream + (none with RegistrationProbe none), plus a registration request when + the probe rejects the client; 5m to 15m suits most upstreams. Empty + keeps the check at start only. + type: string rotation_key: description: |- RotationKey is an optional operator-chosen label (1-64 characters from @@ -267,14 +280,39 @@ components: client_secret_env_var: description: |- ClientSecretEnvVar is the name of an environment variable containing - the client secret. One of ClientSecretFile or ClientSecretEnvVar is - required. + the client secret. Unless the client uses private_key_jwt, one of + ClientSecretFile or ClientSecretEnvVar is required. type: string client_secret_file: description: |- ClientSecretFile is the path to a file containing the client secret. If both this and ClientSecretEnvVar are set, the file takes precedence. + Forbidden with private_key_jwt. + type: string + grant_types: + description: |- + GrantTypes are the grants this client may use. If omitted or empty, it + is [urn:ietf:params:oauth:grant-type:token-exchange]. When set, it must + include token exchange. Allowed values are authorization_code, + refresh_token, the token-exchange grant and the jwt-bearer grant. + items: + type: string + type: array + uniqueItems: false + jwks_file: + description: |- + JWKSFile is the path to a file containing the client's public JWKS. + Required for private_key_jwt and forbidden otherwise. The keys must be + public signing keys. type: string + redirect_uris: + description: |- + RedirectURIs are the registered redirect URIs. Required with the + authorization_code grant and forbidden without it. + items: + type: string + type: array + uniqueItems: false scopes: description: |- Scopes are the OAuth scopes this client may request. Required, and @@ -284,6 +322,18 @@ components: type: string type: array uniqueItems: false + token_endpoint_auth_method: + description: |- + TokenEndpointAuthMethod pins how the client authenticates at the token + endpoint: client_secret_basic, client_secret_post or private_key_jwt. + If omitted, the client authenticates with its secret by either HTTP + Basic or form body. + type: string + token_endpoint_auth_signing_alg: + description: |- + TokenEndpointAuthSigningAlg is the private_key_jwt signing algorithm. + Defaults to RS256. + type: string type: object authserver.IdentityFromTokenRunConfig: description: |- @@ -749,12 +799,13 @@ components: secrets would otherwise travel over cleartext. Defaults to false. Has no effect when there are no confidential clients or Issuer is https. - Applies identically to delegate clients and DCR-registered clients. The - Kubernetes CRD requires the explicit opt-in for a delegate client with an - HTTP issuer; the shared transport validator enforces that its host is - loopback — see EmbeddedAuthServerConfig's doc comment. + Applies identically to every delegate client (including private_key_jwt + delegates) and DCR-registered confidential clients. The Kubernetes CRD + requires the explicit opt-in for a delegate client with an HTTP issuer; + the shared transport validator enforces that its host is loopback — see + EmbeddedAuthServerConfig's doc comment. - private_key_jwt registration has no equivalent flag or transport + DCR private_key_jwt registration has no equivalent flag or transport restriction: unlike confidential registration, it never returns a client_secret (or any other secret) in the DCR response, so there is nothing here for cleartext HTTP to expose. diff --git a/static/api-specs/toolhive-crds/mcpexternalauthconfigs.schema.json b/static/api-specs/toolhive-crds/mcpexternalauthconfigs.schema.json index 5a10f5f2..a3cc22aa 100644 --- a/static/api-specs/toolhive-crds/mcpexternalauthconfigs.schema.json +++ b/static/api-specs/toolhive-crds/mcpexternalauthconfigs.schema.json @@ -1437,6 +1437,17 @@ ], "type": "string" }, + "registrationProbeInterval": { + "description": "RegistrationProbeInterval, when set, repeats the start-time check while\nthe proxy runs, as a duration of at least 1m; 5m to 15m suits most\nupstreams. ToolHive re-reads the cached registration, checks it with\nRegistrationProbe, replaces a rejected one or registers a missing one,\nand switches to the result in place when it differs from the client in\nuse, including when another replica replaced it. Without it, a\nregistration the upstream drops stays in use until the proxy restarts.\nEach check sends at most one RegistrationProbe request to the upstream\nfrom every replica (none with RegistrationProbe none), plus a\nregistration request when the probe rejects the client; the\ntokenEndpoint probe appears in upstream logs as a failed code\nredemption. A re-registration presents the initial access token the pod\nread at start, so an upstream that issues single-use initial access\ntokens still needs a restart to recover.", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$", + "type": "string", + "x-kubernetes-validations": [ + { + "message": "registrationProbeInterval must be at least 1m", + "rule": "duration(self) >= duration('1m')" + } + ] + }, "rotationKey": { "description": "RotationKey is an optional operator-chosen label that is part of the\ncached registration's identity but is never sent to the upstream.\nChange it to make the proxies register a fresh client at their next\nstart instead of reusing the cached one: for example after the\nupstream has dropped the registration, or when a rotation is required.\nAt start ToolHive checks the cached client at the upstream (see\nRegistrationProbe) and registers a new one by itself when the upstream\nrejects it. Where the configured probe cannot tell, the stale client_id\nmay only be rejected in the user's browser, and changing this key is\nthe remedy. The previous registration's row stays in storage, and\nupstream refresh tokens bound to the previous client_id stop\nrefreshing, so affected users sign in again.", "maxLength": 64, @@ -1751,6 +1762,17 @@ ], "type": "string" }, + "registrationProbeInterval": { + "description": "RegistrationProbeInterval, when set, repeats the start-time check while\nthe proxy runs, as a duration of at least 1m; 5m to 15m suits most\nupstreams. ToolHive re-reads the cached registration, checks it with\nRegistrationProbe, replaces a rejected one or registers a missing one,\nand switches to the result in place when it differs from the client in\nuse, including when another replica replaced it. Without it, a\nregistration the upstream drops stays in use until the proxy restarts.\nEach check sends at most one RegistrationProbe request to the upstream\nfrom every replica (none with RegistrationProbe none), plus a\nregistration request when the probe rejects the client; the\ntokenEndpoint probe appears in upstream logs as a failed code\nredemption. A re-registration presents the initial access token the pod\nread at start, so an upstream that issues single-use initial access\ntokens still needs a restart to recover.", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$", + "type": "string", + "x-kubernetes-validations": [ + { + "message": "registrationProbeInterval must be at least 1m", + "rule": "duration(self) >= duration('1m')" + } + ] + }, "rotationKey": { "description": "RotationKey is an optional operator-chosen label that is part of the\ncached registration's identity but is never sent to the upstream.\nChange it to make the proxies register a fresh client at their next\nstart instead of reusing the cached one: for example after the\nupstream has dropped the registration, or when a rotation is required.\nAt start ToolHive checks the cached client at the upstream (see\nRegistrationProbe) and registers a new one by itself when the upstream\nrejects it. Where the configured probe cannot tell, the stale client_id\nmay only be rejected in the user's browser, and changing this key is\nthe remedy. The previous registration's row stays in storage, and\nupstream refresh tokens bound to the previous client_id stop\nrefreshing, so affected users sign in again.", "maxLength": 64, diff --git a/static/api-specs/toolhive-crds/virtualmcpservers.schema.json b/static/api-specs/toolhive-crds/virtualmcpservers.schema.json index e9d2b4ad..dbf1c538 100644 --- a/static/api-specs/toolhive-crds/virtualmcpservers.schema.json +++ b/static/api-specs/toolhive-crds/virtualmcpservers.schema.json @@ -1328,6 +1328,17 @@ ], "type": "string" }, + "registrationProbeInterval": { + "description": "RegistrationProbeInterval, when set, repeats the start-time check while\nthe proxy runs, as a duration of at least 1m; 5m to 15m suits most\nupstreams. ToolHive re-reads the cached registration, checks it with\nRegistrationProbe, replaces a rejected one or registers a missing one,\nand switches to the result in place when it differs from the client in\nuse, including when another replica replaced it. Without it, a\nregistration the upstream drops stays in use until the proxy restarts.\nEach check sends at most one RegistrationProbe request to the upstream\nfrom every replica (none with RegistrationProbe none), plus a\nregistration request when the probe rejects the client; the\ntokenEndpoint probe appears in upstream logs as a failed code\nredemption. A re-registration presents the initial access token the pod\nread at start, so an upstream that issues single-use initial access\ntokens still needs a restart to recover.", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$", + "type": "string", + "x-kubernetes-validations": [ + { + "message": "registrationProbeInterval must be at least 1m", + "rule": "duration(self) >= duration('1m')" + } + ] + }, "rotationKey": { "description": "RotationKey is an optional operator-chosen label that is part of the\ncached registration's identity but is never sent to the upstream.\nChange it to make the proxies register a fresh client at their next\nstart instead of reusing the cached one: for example after the\nupstream has dropped the registration, or when a rotation is required.\nAt start ToolHive checks the cached client at the upstream (see\nRegistrationProbe) and registers a new one by itself when the upstream\nrejects it. Where the configured probe cannot tell, the stale client_id\nmay only be rejected in the user's browser, and changing this key is\nthe remedy. The previous registration's row stays in storage, and\nupstream refresh tokens bound to the previous client_id stop\nrefreshing, so affected users sign in again.", "maxLength": 64, @@ -1642,6 +1653,17 @@ ], "type": "string" }, + "registrationProbeInterval": { + "description": "RegistrationProbeInterval, when set, repeats the start-time check while\nthe proxy runs, as a duration of at least 1m; 5m to 15m suits most\nupstreams. ToolHive re-reads the cached registration, checks it with\nRegistrationProbe, replaces a rejected one or registers a missing one,\nand switches to the result in place when it differs from the client in\nuse, including when another replica replaced it. Without it, a\nregistration the upstream drops stays in use until the proxy restarts.\nEach check sends at most one RegistrationProbe request to the upstream\nfrom every replica (none with RegistrationProbe none), plus a\nregistration request when the probe rejects the client; the\ntokenEndpoint probe appears in upstream logs as a failed code\nredemption. A re-registration presents the initial access token the pod\nread at start, so an upstream that issues single-use initial access\ntokens still needs a restart to recover.", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$", + "type": "string", + "x-kubernetes-validations": [ + { + "message": "registrationProbeInterval must be at least 1m", + "rule": "duration(self) >= duration('1m')" + } + ] + }, "rotationKey": { "description": "RotationKey is an optional operator-chosen label that is part of the\ncached registration's identity but is never sent to the upstream.\nChange it to make the proxies register a fresh client at their next\nstart instead of reusing the cached one: for example after the\nupstream has dropped the registration, or when a rotation is required.\nAt start ToolHive checks the cached client at the upstream (see\nRegistrationProbe) and registers a new one by itself when the upstream\nrejects it. Where the configured probe cannot tell, the stale client_id\nmay only be rejected in the user's browser, and changing this key is\nthe remedy. The previous registration's row stays in storage, and\nupstream refresh tokens bound to the previous client_id stop\nrefreshing, so affected users sign in again.", "maxLength": 64, From 46fe06ccbd14a40c47d63da514ab9026ea34d84f Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Wed, 28 Oct 2026 22:36:32 +0000 Subject: [PATCH 3/4] Document Redis TLS default and DCR re-check Covers operator.defaultRedis.tls (stacklok/toolhive#6838) and dcrConfig.registrationProbeInterval (stacklok/toolhive#6948) for v0.59.0. Co-Authored-By: Claude Opus 5.5 --- docs/toolhive/guides-k8s/deploy-operator.mdx | 39 +++++++++++++++++-- .../guides-k8s/embedded-auth-server-k8s.mdx | 36 ++++++++++++++++- .../guides-vmcp/embedded-auth-server-vmcp.mdx | 15 +++---- 3 files changed, 77 insertions(+), 13 deletions(-) diff --git a/docs/toolhive/guides-k8s/deploy-operator.mdx b/docs/toolhive/guides-k8s/deploy-operator.mdx index b528f589..151ac4b1 100644 --- a/docs/toolhive/guides-k8s/deploy-operator.mdx +++ b/docs/toolhive/guides-k8s/deploy-operator.mdx @@ -306,10 +306,41 @@ resource can still point at a different Redis instance. `existingSecretKey` defaults to `redis-password` when omitted; set it when your Secret stores the password under a different key. -This default applies only to the simple password-authenticated Redis used for -horizontal scaling. It does not configure Sentinel, TLS, or ACL users. For those -use cases, configure session storage explicitly on each workload as described in -[Redis session storage](./redis-session-storage.mdx). +To connect to a Redis endpoint that requires TLS, enable `tls` on the default. +The operator verifies the server certificate against the system roots, or +against a CA certificate you provide in a Secret: + +```yaml title="values.yaml" +operator: + defaultRedis: + addr: redis.example.com:6380 + existingSecret: redis-password + existingSecretKey: password + # highlight-start + tls: + enabled: true + caCertSecretRef: + name: redis-ca + key: ca.crt + # highlight-end +``` + +Omit `caCertSecretRef` to verify against the system roots. `key` defaults to +`ca.crt`. TLS requires a Redis address, and the chart fails to render if you set +a CA Secret without `tls.enabled: true`. The CA Secret must exist in each +workload's namespace, not the operator namespace. If the Secret or key is +missing, affected pods stay in `ContainerCreating` with `FailedMount` events; +run `kubectl describe pod` in the workload namespace to find the missing +reference. + +Workloads load the CA certificate when they start. If you rotate the certificate +in the same Secret, restart the affected workloads to pick it up. A workload +with its own `spec.sessionStorage` uses only its own settings, including TLS, +and ignores the default entirely. + +The default covers a single password-authenticated Redis address, with optional +TLS. For Sentinel or ACL users, configure session storage explicitly on each +workload as described in [Redis session storage](./redis-session-storage.mdx). :::enterprise diff --git a/docs/toolhive/guides-k8s/embedded-auth-server-k8s.mdx b/docs/toolhive/guides-k8s/embedded-auth-server-k8s.mdx index 30a60c2c..ea68dbf6 100644 --- a/docs/toolhive/guides-k8s/embedded-auth-server-k8s.mdx +++ b/docs/toolhive/guides-k8s/embedded-auth-server-k8s.mdx @@ -1028,7 +1028,9 @@ dcrConfig: ToolHive caches the client it registers and reuses it across restarts. Each time the proxy starts, ToolHive checks the cached client at the upstream. If the upstream rejects it, ToolHive registers a new client and replaces the cached one -automatically. +automatically. To repeat this check while the proxy runs, set +`registrationProbeInterval` (see +[Re-check the registration while running](#re-check-the-registration-while-running)). Choose how ToolHive runs that check with `registrationProbe` in `dcrConfig`: @@ -1041,7 +1043,8 @@ Choose how ToolHive runs that check with `registrationProbe` in `dcrConfig`: Use `authorizationEndpoint` for upstreams whose token endpoint answers `invalid_grant` for any client ID, live or dropped. The probe sends no client credentials, so it also covers `private_key_jwt` clients. The upstream sees one -abandoned authorization request each time the proxy starts. +abandoned authorization request each time the proxy starts, and on each periodic +check if you set `registrationProbeInterval`. ```yaml title="MCPExternalAuthConfig: probe the authorization endpoint" dcrConfig: @@ -1071,6 +1074,35 @@ the upstream. When the value changes, the proxy registers a new client at its next start. Upstream refresh tokens issued to the previous client stop working, so affected users must sign in again. +#### Re-check the registration while running + +By default, a registration that the upstream drops while the proxy runs stays in +use until the proxy restarts. To detect and replace it without a restart, set +`registrationProbeInterval` in `dcrConfig` to a duration of at least `1m`. An +interval between `5m` and `15m` suits most upstreams: + +```yaml title="MCPExternalAuthConfig: re-check the DCR client every 10 minutes" +dcrConfig: + discoveryUrl: 'https://mcp.example.com/.well-known/oauth-authorization-server' + # highlight-next-line + registrationProbeInterval: 10m +``` + +On each check, ToolHive re-reads the cached registration and checks it with the +configured `registrationProbe`. It replaces a rejected registration, registers a +new client if the cached one is missing, and switches to the result in place +when it differs from the client in use, including when another replica already +replaced it. + +Every replica runs its own checks. Each check sends at most one probe request to +the upstream (none when `registrationProbe` is `none`), plus a registration +request when the probe rejects the client. With the `tokenEndpoint` probe, each +check appears in the upstream's logs as a failed code redemption. + +A re-registration presents the initial access token the pod read at start. If +your upstream issues single-use initial access tokens, restart the proxy to +recover from a dropped registration. + ### Omit scopes on token refresh By default, the embedded authorization server sends the configured `scopes` when diff --git a/docs/toolhive/guides-vmcp/embedded-auth-server-vmcp.mdx b/docs/toolhive/guides-vmcp/embedded-auth-server-vmcp.mdx index 2e1f469d..bef9e31a 100644 --- a/docs/toolhive/guides-vmcp/embedded-auth-server-vmcp.mdx +++ b/docs/toolhive/guides-vmcp/embedded-auth-server-vmcp.mdx @@ -577,13 +577,14 @@ registration endpoint bypasses discovery, so keep the authorization endpoint, token endpoint, and scopes in the parent `oauth2Config`. With `dcrConfig`, ToolHive obtains the client ID and secret from the DCR response. -| Field | Description | -| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `dcrConfig.discoveryUrl` | HTTPS URL of an RFC 8414 or OpenID Connect discovery document. | -| `dcrConfig.registrationEndpoint` | HTTPS URL of the RFC 7591 registration endpoint. | -| `dcrConfig.initialAccessTokenRef` | Optional reference to a Secret whose value ToolHive sends as a bearer token when it calls the registration endpoint. Requires `name` and `key`. | -| `dcrConfig.registrationProbe` | How ToolHive checks the cached client at start: `tokenEndpoint` (default), `authorizationEndpoint`, or `none`; see [Re-register a dropped upstream client](../guides-k8s/embedded-auth-server-k8s.mdx#re-register-a-dropped-upstream-client). | -| `dcrConfig.rotationKey` | Optional label (1-64 letters, digits, `.`, `_`, or `-`). Change it to force a fresh registration at the next start; see [Re-register a dropped upstream client](../guides-k8s/embedded-auth-server-k8s.mdx#re-register-a-dropped-upstream-client). | +| Field | Description | +| ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `dcrConfig.discoveryUrl` | HTTPS URL of an RFC 8414 or OpenID Connect discovery document. | +| `dcrConfig.registrationEndpoint` | HTTPS URL of the RFC 7591 registration endpoint. | +| `dcrConfig.initialAccessTokenRef` | Optional reference to a Secret whose value ToolHive sends as a bearer token when it calls the registration endpoint. Requires `name` and `key`. | +| `dcrConfig.registrationProbe` | How ToolHive checks the cached client: `tokenEndpoint` (default), `authorizationEndpoint`, or `none`; see [Re-register a dropped upstream client](../guides-k8s/embedded-auth-server-k8s.mdx#re-register-a-dropped-upstream-client). | +| `dcrConfig.registrationProbeInterval` | Optional duration of at least `1m`, such as `10m`. Repeats the check while vMCP runs and switches to a replacement client without a restart; see [Re-check the registration while running](../guides-k8s/embedded-auth-server-k8s.mdx#re-check-the-registration-while-running). | +| `dcrConfig.rotationKey` | Optional label (1-64 letters, digits, `.`, `_`, or `-`). Change it to force a fresh registration at the next start; see [Re-register a dropped upstream client](../guides-k8s/embedded-auth-server-k8s.mdx#re-register-a-dropped-upstream-client). | To stop sending `scope` on refresh token requests to providers that reject it, such as Salesforce, set `omitScopeOnRefresh: true` on the upstream's From 8dba4a7ef83217f5ae1e8a09ec930e98f951b1c7 Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Wed, 28 Oct 2026 22:38:04 +0000 Subject: [PATCH 4/4] Clarify DCR registration re-check wording Co-Authored-By: Claude Opus 5.5 --- docs/toolhive/guides-k8s/embedded-auth-server-k8s.mdx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/toolhive/guides-k8s/embedded-auth-server-k8s.mdx b/docs/toolhive/guides-k8s/embedded-auth-server-k8s.mdx index ea68dbf6..5df0fb6b 100644 --- a/docs/toolhive/guides-k8s/embedded-auth-server-k8s.mdx +++ b/docs/toolhive/guides-k8s/embedded-auth-server-k8s.mdx @@ -1089,10 +1089,10 @@ dcrConfig: ``` On each check, ToolHive re-reads the cached registration and checks it with the -configured `registrationProbe`. It replaces a rejected registration, registers a -new client if the cached one is missing, and switches to the result in place -when it differs from the client in use, including when another replica already -replaced it. +configured `registrationProbe`. It replaces a rejected registration and +registers a new client if the cached one is missing. When the resulting client +differs from the one in use, including when another replica already replaced it, +the proxy switches to the new client without restarting. Every replica runs its own checks. Each check sends at most one probe request to the upstream (none when `registrationProbe` is `none`), plus a registration