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.55.2
version: v0.56.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
3 changes: 2 additions & 1 deletion docs/toolhive/concepts/embedded-auth-server.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,8 @@ the OAuth flow.
support CIMD (such as VS Code) use it automatically.
- **Dynamic Client Registration (DCR):** Supports OAuth 2.0 DCR (RFC 7591) as a
fallback for clients that do not support CIMD. MCP clients register
automatically with no manual configuration in ToolHive.
automatically with no manual configuration in ToolHive. A client cap and a
rate limit on `/oauth/register` bound how many clients can register.
- **Direct upstream redirect:** Redirects clients directly to the upstream
provider for authentication (for example, GitHub or Atlassian).
- **Configurable signing keys:** JWTs are signed with keys you provide,
Expand Down
9 changes: 6 additions & 3 deletions docs/toolhive/guides-cli/client-configuration.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -127,9 +127,12 @@ thv client connector rm --client claude-desktop github-tools
Re-running `thv llm setup --client claude-desktop` keeps your connectors, and
`thv llm teardown` removes them along with the rest of the gateway
configuration. `thv client connector add` logs a warning when Claude Desktop
won't load the connector, for example when a managed-preferences (MDM) profile
overrides the local configuration or a different saved configuration is applied
in the app.
won't load the connector, for example when a managed configuration overrides the
local configuration or a different saved configuration is applied in the app. A
managed configuration is an MDM profile on macOS, or
`/etc/claude-desktop/managed-settings.json` on Linux. A managed file that Claude
Desktop rejects as invalid also disables the local configuration and triggers
the same warning.

See the
[`thv client connector` reference](../reference/cli/thv_client_connector.md) for
Expand Down
66 changes: 58 additions & 8 deletions docs/toolhive/guides-k8s/embedded-auth-server-k8s.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -397,11 +397,11 @@ For a complete walkthrough including deploying Redis Sentinel from scratch, see
## Configure MCP client registration

MCP clients must identify themselves to the embedded authorization server. The
server always accepts Dynamic Client Registration (DCR) requests from MCP
clients; no configuration is required. Enable CIMD for clients that support the
MCP specification's preferred registration mechanism. You can also set baseline
scopes for clients that need to request scopes beyond those in their
registration metadata.
server accepts Dynamic Client Registration (DCR) requests from MCP clients with
no configuration required, subject to configurable registration limits. Enable
CIMD for clients that support the MCP specification's preferred registration
mechanism. You can also set baseline scopes for clients that need to request
scopes beyond those in their registration metadata.

### Enable CIMD for zero-registration clients

Expand Down Expand Up @@ -461,6 +461,56 @@ For the two-layer trust model behind CIMD (why the upstream IdP never sees the
client's CIMD URL), see
[Client ID Metadata Document (CIMD)](../concepts/embedded-auth-server.mdx#client-id-metadata-document-cimd).

### Limit Dynamic Client Registration

Because `/oauth/register` is unauthenticated, the embedded authorization server
bounds how many clients it registers and how many it keeps:

- **Expiry:** a client that registers but never completes a token exchange
expires after 24 hours. Its first successful token exchange extends the
registration to 30 days, and a registration idle for more than 30 days
expires. An expired client must register again.
- **Client cap:** `storage.maxDCRClients` caps the number of live DCR-registered
clients (default `10000`). At the cap, `/oauth/register` returns HTTP `503`
with a `Retry-After` header until a client expires; no existing client is
evicted. Operator-configured clients and CIMD clients don't count toward the
cap. With Redis storage, every replica shares the count; with in-memory
storage, each replica keeps its own.
- **Rate limit:** `registrationRateLimit` is a token bucket that limits
registrations per replica (default 1 per second with a burst of 5). Requests
over the limit get HTTP `429` with a `Retry-After` header. Set `maxTokens` to
the burst size and `refillPeriod` to the time it takes to refill the bucket
from empty. For example, 10 registrations per minute with a burst of 3 is
`maxTokens: 3` and `refillPeriod: 18s`.

The defaults suit most deployments. Raise the limits if many legitimate clients
register in a short window, such as during a large client rollout:

```yaml title="MCPExternalAuthConfig: DCR limits"
spec:
embeddedAuthServer:
registrationRateLimit:
maxTokens: 10
refillPeriod: 10s
storage:
type: redis
maxDCRClients: 25000
redis:
# ...
```

When you size the cap, keep in mind that unused clients live for 24 hours. A
caller registering at the full rate can keep up to the per-second rate,
multiplied by the replica count and by 86,400 (the seconds in a day), unused
clients alive. At the default rate, one replica fills the default cap in about
2.8 hours, and new registrations then fail with `503` until those clients
expire.

Setting `maxDCRClients: 0` removes the cap, leaving the rate limit as the only
bound on stored clients. Use it only temporarily, for example while an existing
client population above the cap drains. A large finite value, such as two to
three times your observed live client count, keeps some protection.

### Allow confidential DCR clients

By default, DCR only issues public clients: registrations must use
Expand All @@ -477,9 +527,9 @@ spec:

`/oauth/register` is unauthenticated, so enabling this lets any caller who can
reach the endpoint mint a client credential. Confidential registrations are
restricted to `https` non-loopback redirect URIs, and (with the Redis storage
backend) any DCR registration idle for more than 30 days is evicted and must
re-register.
restricted to `https` non-loopback redirect URIs, and they expire like any other
DCR registration (see
[Limit Dynamic Client Registration](#limit-dynamic-client-registration)).

To let an agent register a keypair for RFC 8693 token exchange instead of
receiving a shared secret, set `allowPrivateKeyJWTRegistration: true`:
Expand Down
25 changes: 25 additions & 0 deletions docs/toolhive/guides-vmcp/embedded-auth-server-vmcp.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,31 @@ For an explanation of how ToolHive resolves CIMD documents and why the OAuth
provider never sees the MCP client's CIMD URL, see
[Client ID Metadata Document (CIMD)](../concepts/embedded-auth-server.mdx#client-id-metadata-document-cimd).

### Limit Dynamic Client Registration

The embedded authorization server caps the number of live DCR clients (default
`10000`) and rate-limits `/oauth/register` (default 1 registration per second
with a burst of 5, per replica). To change these limits, set
`storage.maxDCRClients` and `registrationRateLimit` under
`spec.authServerConfig`:

```yaml title="VirtualMCPServer: DCR limits"
spec:
authServerConfig:
registrationRateLimit:
maxTokens: 10
refillPeriod: 10s
storage:
type: redis
maxDCRClients: 25000
redis:
# ...
```

For client expiry rules, sizing guidance, and the HTTP responses at each limit,
see
[Limit Dynamic Client Registration](../guides-k8s/embedded-auth-server-k8s.mdx#limit-dynamic-client-registration).

### Allow confidential DCR clients

To configure a VirtualMCPServer for confidential DCR, set
Expand Down
7 changes: 5 additions & 2 deletions docs/toolhive/reference/cli/thv_client_connector_add.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,11 @@ must be the server's final address: Claude Desktop does not follow HTTP
redirects, even one that only adds a trailing slash.

A warning is logged when Claude Desktop will not load the connector: when a
managed-preferences (MDM) profile overrides its local configuration, or when a
different saved configuration is applied in the app.
managed configuration (an MDM profile on macOS, or
/etc/claude-desktop/managed-settings.json on Linux) overrides its local
configuration, or when a different saved configuration is applied in the app.
A managed file that Claude Desktop rejects as invalid also disables the local
configuration, so it triggers the same warning.

Example:
thv client connector add --client claude-desktop stacklok-connectors https://connector-gateway.example.com/gw/mcp
Expand Down
4 changes: 2 additions & 2 deletions docs/toolhive/reference/cli/thv_llm_config_set.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,14 @@ thv llm config set [flags]

```
--audience string OIDC audience (optional)
--bedrock-compat Persist Bedrock compatibility for Claude Code (CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS=1 + per-tier Bedrock model IDs). Applied by "thv llm setup". Use --bedrock-compat=false to clear.
--bedrock-compat Persist Bedrock compatibility for Claude Code (CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS=1 + per-tier Bedrock model IDs discovered from the gateway). Applied by "thv llm setup". Use --bedrock-compat=false to clear.
--callback-port int OIDC callback port (omit to keep current; default: ephemeral)
--client-id string OIDC client ID
--enable-1m With Bedrock compat, opt into the 1M context window by appending [1m] to opus/sonnet model IDs.
--gateway-url string LLM gateway base URL (must use HTTPS)
-h, --help help for set
--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 is also mapped to a Claude Code tier by matching 'haiku', 'opus', or 'sonnet' in the ID.
--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)
--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
19 changes: 13 additions & 6 deletions docs/toolhive/reference/cli/thv_llm_setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,17 @@ run and persisted to config only after login and tool patching succeed. This
lets you combine "config set" and "setup" into a single command.

For a gateway that forwards to AWS Bedrock, add --bedrock-compat to configure
Claude Code with CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS=1 and per-tier Bedrock
model IDs (override with --models; add --enable-1m for the 1M context window on
opus/sonnet). The setting is persisted and only affects Claude Code. To add it to
an already-configured Claude Code, re-run:
Claude Code with CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS=1 and pin each model tier
to the newest Bedrock model the gateway advertises (override a tier with --models;
add --enable-1m for the 1M context window on opus/sonnet). The pins repoint Claude
Code's built-in Opus/Sonnet/Haiku/Fable rows in the /model picker (via
ANTHROPIC_DEFAULT_*_MODEL), so selecting a built-in row routes to Bedrock. Setup
also enables Claude Code's gateway model discovery, which adds any further models
the gateway serves as separate picker rows without re-running setup. If the
gateway's model list cannot be fetched, or has no Bedrock model IDs, setup does
not configure Claude Code (unless --models pins every tier). The setting is
persisted and only affects Claude Code.
To add it to an already-configured Claude Code, re-run:

thv llm setup --client claude-code --bedrock-compat

Expand All @@ -80,7 +87,7 @@ thv llm setup [flags]
```
--anthropic-path-prefix string Path prefix appended to the gateway URL for Anthropic API tools (e.g. /anthropic). Also used for Pi model discovery. When omitted, the gateway is probed automatically.
--audience string OIDC audience (optional)
--bedrock-compat Configure Claude Code for a gateway that forwards to AWS Bedrock: write CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS=1 (Bedrock rejects the experimental anthropic-beta headers) and pin per-tier Bedrock model IDs (override with --models). Persisted, so a later plain "thv llm setup" keeps it; clear with --bedrock-compat=false. Only affects Claude Code.
--bedrock-compat Configure Claude Code for a gateway that forwards to AWS Bedrock: write CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS=1 (Bedrock rejects the experimental anthropic-beta headers) and pin each tier to the newest Bedrock model the gateway advertises (override with --models). Persisted, so a later plain "thv llm setup" keeps it; clear with --bedrock-compat=false. Only affects Claude Code.
--callback-port int OIDC callback port (omit to keep current; default: ephemeral)
--client string Configure only this AI tool by name (e.g. claude-code, cursor, codex). Omit to configure all detected tools.
--client-id string OIDC client ID
Expand All @@ -89,7 +96,7 @@ thv llm setup [flags]
-h, --help help for setup
--issuer string OIDC issuer URL
--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 is also mapped to a Claude Code tier by matching 'haiku', 'opus', or 'sonnet' in the ID (IDs matching no tier are ignored with a warning). Omit to use the built-in Bedrock defaults / gateway model discovery.
--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)
--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.
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.55.2`. Source:
[`values.yaml`](https://github.com/stacklok/toolhive/blob/v0.55.2/deploy/charts/operator-crds/values.yaml)
at `v0.55.2`.
Chart: `toolhive-operator-crds`. Chart version: `0.56.0`. Source:
[`values.yaml`](https://github.com/stacklok/toolhive/blob/v0.56.0/deploy/charts/operator-crds/values.yaml)
at `v0.56.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.55.2`. Source:
[`values.yaml`](https://github.com/stacklok/toolhive/blob/v0.55.2/deploy/charts/operator/values.yaml)
at `v0.55.2`.
Chart: `toolhive-operator`. Chart version: `0.56.0`. Source:
[`values.yaml`](https://github.com/stacklok/toolhive/blob/v0.56.0/deploy/charts/operator/values.yaml)
at `v0.56.0`.

## Values

Expand Down Expand Up @@ -68,7 +68,7 @@ features:
gc:
gogc: 75
gomemlimit: 110MiB
image: ghcr.io/stacklok/toolhive/operator:v0.55.2
image: ghcr.io/stacklok/toolhive/operator:v0.56.0
imageDiscovery:
enabled: false
resources: {}
Expand Down Expand Up @@ -153,8 +153,8 @@ serviceAccount:
labels: {}
name: toolhive-operator
tolerations: []
toolhiveRunnerImage: ghcr.io/stacklok/toolhive/proxyrunner:v0.55.2
vmcpImage: ghcr.io/stacklok/toolhive/vmcp:v0.55.2
toolhiveRunnerImage: ghcr.io/stacklok/toolhive/proxyrunner:v0.56.0
vmcpImage: ghcr.io/stacklok/toolhive/vmcp:v0.56.0
volumeMounts: []
volumes: []
```
Expand Down Expand Up @@ -255,7 +255,7 @@ Key containing the password in `existingSecret`. When empty, uses

**Type:** `string`

**Default:** `"ghcr.io/stacklok/toolhive/operator:v0.55.2"`
**Default:** `"ghcr.io/stacklok/toolhive/operator:v0.56.0"`

Container image for the operator.

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

**Type:** `string`

**Default:** `"ghcr.io/stacklok/toolhive/proxyrunner:v0.55.2"`
**Default:** `"ghcr.io/stacklok/toolhive/proxyrunner:v0.56.0"`

Container image for ToolHive proxy runners created by the operator.

### `operator.vmcpImage`

**Type:** `string`

**Default:** `"ghcr.io/stacklok/toolhive/vmcp:v0.55.2"`
**Default:** `"ghcr.io/stacklok/toolhive/vmcp:v0.56.0"`

Container image for Virtual MCP Server (vMCP) deployments created by the
operator.
Expand Down
Loading
Loading