Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/upstream-projects.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ projects:

- id: toolhive
repo: stacklok/toolhive
version: v0.59.0
version: v0.60.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/,
Expand Down
30 changes: 28 additions & 2 deletions docs/toolhive/guides-k8s/embedded-auth-server-k8s.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -832,8 +832,34 @@ oauth2Config:
tokenEndpointAuthMethod: client_secret_basic
```

Allowed values are `none`, `client_secret_basic`, and `client_secret_post`. DCR
clients continue to use the method negotiated at registration time.
Allowed values are `none`, `client_secret_basic`, and `client_secret_post`.

With [`dcrConfig`](#use-dynamic-client-registration-with-an-upstream-provider),
ToolHive picks a method from the upstream's advertised
`token_endpoint_auth_methods_supported`, preferring `client_secret_basic`, then
`client_secret_post`, then `none`, and registers with it. Some upstreams
advertise `client_secret_basic` but read client credentials only from the
request body, so every code exchange fails with an error such as
`invalid_client: Missing client_id`. For these upstreams, set
`tokenEndpointAuthMethod` alongside `dcrConfig` to choose the method ToolHive
registers with:

```yaml title="MCPExternalAuthConfig: DCR with a chosen auth method"
oauth2Config:
authorizationEndpoint: 'https://mcp.example.com/authorize'
tokenEndpoint: 'https://mcp.example.com/token'
# highlight-next-line
tokenEndpointAuthMethod: client_secret_post
dcrConfig:
discoveryUrl: 'https://mcp.example.com/.well-known/oauth-authorization-server'
```

ToolHive refuses to start when the upstream's discovery metadata lists supported
methods that don't include the configured one, when you set `none` and the
upstream doesn't advertise S256 PKCE, or when the registration response names a
different method. If ToolHive cached a registration made with a different
method, it replaces that registration with a new one at the next start, so you
don't need to change `rotationKey`.

### Trust a private CA for the upstream provider

Expand Down
12 changes: 10 additions & 2 deletions docs/toolhive/guides-vmcp/embedded-auth-server-vmcp.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -601,8 +601,16 @@ method.
Set `tokenEndpointAuthMethod: client_secret_basic` on `oauth2Config` for
providers that require credentials in the HTTP `Authorization` header instead of
the request body. Allowed values are `none`, `client_secret_basic`, and
`client_secret_post`. DCR clients continue to use the method negotiated at
registration time.
`client_secret_post`.

With `dcrConfig`, ToolHive registers with the method it prefers from the
upstream's advertised methods (`client_secret_basic`, then `client_secret_post`,
then `none`). When the upstream's token endpoint doesn't accept that method, set
`tokenEndpointAuthMethod` alongside `dcrConfig` to choose the method ToolHive
registers with. ToolHive replaces a cached registration made with a different
method at the next start. See
[Select the token endpoint auth method](../guides-k8s/embedded-auth-server-k8s.mdx#select-the-token-endpoint-auth-method)
for the validation rules.

### Authorize upstream registration with an initial access token

Expand Down
1 change: 1 addition & 0 deletions docs/toolhive/reference/cli/thv_llm_config_set.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ thv llm config set [flags]
--issuer string OIDC issuer URL
--models strings Model IDs to persist and apply during "thv llm setup", comma-separated or by repeating the flag, e.g. --models=us.anthropic.claude-opus-4-8,us.anthropic.claude-sonnet-5. Credential-helper clients (Claude Desktop) write these as inferenceModels; with Bedrock compat, each ID overrides a Claude Code tier by matching 'haiku', 'opus', 'sonnet', or 'fable' in the ID.
--proxy-port int Localhost proxy listen port (omit to keep current; default: 14000)
--scopes strings OIDC scopes to request, comma-separated or by repeating the flag. offline_access is always requested; pass openid explicitly if the provider needs it (the default scopes are openid and offline_access). Omit to keep the current scopes; an empty value restores the defaults. Changing the list clears cached tokens so the next sign-in requests the new scopes
--short-prompt-cache Use Claude Code's five-minute prompt cache instead of ToolHive's one-hour default. Applied by "thv llm setup"; use --short-prompt-cache=false to restore one-hour caching.
--tls-skip-verify Skip TLS certificate verification for the upstream gateway (local dev only; use --tls-skip-verify=false to clear)
```
Expand Down
1 change: 1 addition & 0 deletions docs/toolhive/reference/cli/thv_llm_setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ thv llm setup [flags]
--lazy Skip the interactive OIDC login and defer it until the first time a configured tool accesses the gateway. Tool config and persisted settings are written normally. Useful for unattended provisioning (e.g. an MDM profile).
--models strings Model IDs to configure, comma-separated or by repeating the flag, e.g. --models=us.anthropic.claude-opus-4-8,us.anthropic.claude-sonnet-5. For Pi these filter its discovered models (Pi does not support --lazy). For credential-helper clients (Claude Desktop) these become inferenceModels. With --bedrock-compat, each ID overrides a Claude Code tier by matching 'haiku', 'opus', 'sonnet', or 'fable' in the ID (IDs matching no tier are ignored with a warning). Omit to pin each tier to the newest Bedrock model discovered from the gateway.
--proxy-port int Localhost proxy listen port (omit to keep current; default: 14000)
--scopes strings OIDC scopes to request, comma-separated or by repeating the flag. offline_access is always requested; pass openid explicitly if the provider needs it (the default scopes are openid and offline_access). Omit to keep the current scopes; an empty value restores the defaults. Changing the list clears cached tokens so the next sign-in requests the new scopes
--short-prompt-cache Use Claude Code's five-minute prompt cache instead of ToolHive's one-hour default. Persisted, so a later plain "thv llm setup" keeps it; use --short-prompt-cache=false to restore one-hour caching.
--skip-browser Print the OIDC authorization URL instead of opening a browser, then wait for the callback. Use in headless/SSH/CI environments where no system browser is available.
--tls-skip-verify Skip TLS certificate verification for the upstream gateway (local dev only). For direct-mode tools (Claude Code, Gemini CLI) this sets NODE_TLS_REJECT_UNAUTHORIZED=0, disabling TLS for ALL of that tool's outbound connections. For proxy-mode tools only the proxy-to-gateway connection is affected.
Expand Down
6 changes: 3 additions & 3 deletions docs/toolhive/reference/helm/operator-crds.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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.59.0`. Source:
[`values.yaml`](https://github.com/stacklok/toolhive/blob/v0.59.0/deploy/charts/operator-crds/values.yaml)
at `v0.59.0`.
Chart: `toolhive-operator-crds`. Chart version: `0.60.0`. Source:
[`values.yaml`](https://github.com/stacklok/toolhive/blob/v0.60.0/deploy/charts/operator-crds/values.yaml)
at `v0.60.0`.

## Values

Expand Down
18 changes: 9 additions & 9 deletions docs/toolhive/reference/helm/operator.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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.59.0`. Source:
[`values.yaml`](https://github.com/stacklok/toolhive/blob/v0.59.0/deploy/charts/operator/values.yaml)
at `v0.59.0`.
Chart: `toolhive-operator`. Chart version: `0.60.0`. Source:
[`values.yaml`](https://github.com/stacklok/toolhive/blob/v0.60.0/deploy/charts/operator/values.yaml)
at `v0.60.0`.

## Values

Expand Down Expand Up @@ -73,7 +73,7 @@ features:
gc:
gogc: 75
gomemlimit: 110MiB
image: ghcr.io/stacklok/toolhive/operator:v0.59.0
image: ghcr.io/stacklok/toolhive/operator:v0.60.0
imageDiscovery:
enabled: false
resources: {}
Expand Down Expand Up @@ -158,8 +158,8 @@ serviceAccount:
labels: {}
name: toolhive-operator
tolerations: []
toolhiveRunnerImage: ghcr.io/stacklok/toolhive/proxyrunner:v0.59.0
vmcpImage: ghcr.io/stacklok/toolhive/vmcp:v0.59.0
toolhiveRunnerImage: ghcr.io/stacklok/toolhive/proxyrunner:v0.60.0
vmcpImage: ghcr.io/stacklok/toolhive/vmcp:v0.60.0
volumeMounts: []
volumes: []
```
Expand Down Expand Up @@ -318,7 +318,7 @@ Key containing the PEM-encoded CA certificate in the Secret.

**Type:** `string`

**Default:** `"ghcr.io/stacklok/toolhive/operator:v0.59.0"`
**Default:** `"ghcr.io/stacklok/toolhive/operator:v0.60.0"`

Container image for the operator.

Expand All @@ -334,15 +334,15 @@ Image pull policy for the operator container and image-discovery containers.

**Type:** `string`

**Default:** `"ghcr.io/stacklok/toolhive/proxyrunner:v0.59.0"`
**Default:** `"ghcr.io/stacklok/toolhive/proxyrunner:v0.60.0"`

Container image for ToolHive proxy runners created by the operator.

### `operator.vmcpImage`

**Type:** `string`

**Default:** `"ghcr.io/stacklok/toolhive/vmcp:v0.59.0"`
**Default:** `"ghcr.io/stacklok/toolhive/vmcp:v0.60.0"`

Container image for Virtual MCP Server (vMCP) deployments created by the
operator.
Expand Down
89 changes: 83 additions & 6 deletions static/api-specs/toolhive-api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,9 @@ components:
for upstreams whose token endpoint answers invalid_grant for any
client_id; an HTML page or a bare gateway status is not a verdict. A
false rejection re-registers at every start and, when registration also
fails, fails startup. "none" skips the check; the registration is then
fails, fails startup. "none" skips the check: the upstream is not
contacted, but a registration made with another method than a configured
TokenEndpointAuthMethod is still replaced. Otherwise the registration is
retired only through RotationKey.
type: string
registration_probe_interval:
Expand Down Expand Up @@ -261,6 +263,24 @@ components:
server trusts.
type: string
type: object
authserver.DelegateClientExchangeRefreshTokens:
description: |-
ExchangeRefreshTokens enables refresh-token issuance through token
exchange for this client. Presence of the block enables it; a nil block
refuses both grant kinds. Valid only with restricted, and requires
APIAudience.
properties:
id_jag_max_lifetime:
description: |-
IDJAGMaxLifetime caps the lifetime of grants that can be exchanged for
ID-JAGs. Empty refuses those grants.
type: string
refresh_token_max_lifetime:
description: |-
RefreshTokenMaxLifetime caps the lifetime of grants that can be exchanged
for child refresh tokens. Empty refuses those grants.
type: string
type: object
authserver.DelegateClientRunConfig:
properties:
audiences:
Expand Down Expand Up @@ -291,10 +311,14 @@ components:
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.
GrantTypes are the grants this client may use. If omitted or empty, the
client is stored with [urn:ietf:params:oauth:grant-type:token-exchange]
and its ID-JAG redemption (a JWT-bearer assertion of typ
oauth-id-jag+jwt) is not gated, for backward compatibility. When set, the
list is complete: it must include token exchange, and it must include the
jwt-bearer grant to keep ID-JAG redemption. Allowed values are
authorization_code, refresh_token, the token-exchange grant and the
jwt-bearer grant.
items:
type: string
type: array
Expand Down Expand Up @@ -332,8 +356,56 @@ components:
token_endpoint_auth_signing_alg:
description: |-
TokenEndpointAuthSigningAlg is the private_key_jwt signing algorithm.
Defaults to RS256.
If omitted, it is derived from the keys: each key's alg, or ES256, ES384
or ES512 from an EC key's curve. RSA keys without alg use RS256. Keys
that disagree must set it.
type: string
token_exchange:
$ref: '#/components/schemas/authserver.DelegateClientTokenExchange'
type: object
authserver.DelegateClientTokenExchange:
description: |-
TokenExchange is the client's token-exchange policy. Its
SelfIssuedSubjectTokens field selects which self-issued subject tokens
the client may exchange and is required whenever the block is present.
The block may be omitted only by a secret-authenticated client whose
GrantTypes is omitted or empty; such a client keeps the legacy rule (any)
and may exchange any self-issued token. A client that sets GrantTypes or
uses private_key_jwt must declare the block.
properties:
allowed_source_clients:
description: |-
AllowedSourceClients are client_id values, as they appear in the subject
token's client_id claim, whose self-issued tokens this client may
exchange in addition to its own. They must be user-facing clients that
register at runtime (DCR or CIMD), so startup cannot check that they
exist. A configured delegate client cannot be listed: its tokens carry
its own client_id, so listing it would trust everything it accepts.
Valid only with restricted; empty means the client's own tokens only,
except that exchange_refresh_tokens requires a non-empty list and does
not accept the client's own tokens.
items:
type: string
type: array
uniqueItems: false
api_audience:
description: |-
APIAudience is the client's own API audience. The client may request it
even when the subject token does not cover it. It must be one of the
client's Audiences. Valid only with restricted.
type: string
exchange_refresh_tokens:
$ref: '#/components/schemas/authserver.DelegateClientExchangeRefreshTokens'
self_issued_subject_tokens:
description: |-
SelfIssuedSubjectTokens is any or restricted, and is required. With
restricted, a self-issued subject token must carry the client's own
client_id or one of AllowedSourceClients, and a may_act claim naming
this client does not lift that restriction. With any, the client may
exchange any self-issued token.
type: string
required:
- self_issued_subject_tokens
type: object
authserver.IdentityFromTokenRunConfig:
description: |-
Expand Down Expand Up @@ -501,6 +573,11 @@ components:
client_secret_post-shaped default). Set this to client_secret_basic explicitly
for providers that require HTTP Basic auth. Public clients without a secret use
the "none" method.
With DCRConfig, it is the method the client registers with, in place of the one
ToolHive picks from the upstream's advertised methods (client_secret_basic, then
client_secret_post, then none). Set it for an upstream whose token endpoint does
not accept the method ToolHive picks. A cached registration made with another
method is replaced with a new one at the next start.
type: string
token_response_mapping:
$ref: '#/components/schemas/authserver.TokenResponseMappingRunConfig'
Expand Down
Loading
Loading