Skip to content
Open
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 @@ -35,7 +35,7 @@ projects:

- id: toolhive
repo: stacklok/toolhive
version: v0.26.1
version: v0.27.1
# 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
22 changes: 17 additions & 5 deletions docs/toolhive/guides-cli/api-server.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -57,17 +57,29 @@ address to bind to using the `--host` option:
thv serve --host <HOSTNAME_OR_IP>
```

## UNIX socket support
## UNIX socket and Windows named-pipe support

The API server can also be exposed via a UNIX socket instead of a TCP port. Use
the `--socket` option to specify a socket path:
The API server can also be exposed via a UNIX socket or Windows named pipe
instead of a TCP port. Use the `--socket` option to specify the address.

On macOS and Linux, pass a filesystem path for a UNIX domain socket:

```bash
thv serve --socket /tmp/toolhive.sock
```

When using a UNIX socket, the `--socket` argument overrides the host:port
address configuration.
On Windows, pass a named-pipe path:

```powershell
thv serve --socket \\.\pipe\thv-api
```

ToolHive auto-detects the address type from its prefix: addresses starting with
`\\.\pipe\` open as a Windows named pipe; anything else opens as a UNIX domain
socket. Named-pipe addresses are rejected on non-Windows platforms.

When using `--socket`, the argument overrides the host:port address
configuration.

## API documentation

Expand Down
27 changes: 18 additions & 9 deletions docs/toolhive/guides-cli/run-mcp-servers.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -684,9 +684,21 @@ thv run https://protected-api.com/mcp --name my-server
```

If authentication is required, ToolHive will prompt you to complete the OAuth
flow. When no client credentials are provided, ToolHive automatically registers
an OAuth client with the authorization server using RFC 7591 dynamic client
registration, eliminating the need to pre-configure client ID and secret.
flow. When no client credentials are provided, ToolHive identifies itself to the
authorization server using one of two mechanisms:

- **Client ID Metadata Document (CIMD).** If the authorization server's
discovery document sets `client_id_metadata_document_supported: true`,
ToolHive presents
[`https://toolhive.dev/oauth/client-metadata.json`](https://toolhive.dev/oauth/client-metadata.json)
as its `client_id`. No registration round-trip is needed.
- **Dynamic Client Registration (DCR, RFC 7591).** When CIMD is unavailable or
the authorization server rejects the CIMD `client_id` with
`invalid_client`/`unauthorized_client`, ToolHive falls back to registering an
OAuth client dynamically.

Either path eliminates the need to pre-configure a client ID and secret for
authorization servers that support them.

#### OIDC authentication

Expand Down Expand Up @@ -760,12 +772,9 @@ as defined by [RFC 8707](https://datatracker.ietf.org/doc/html/rfc8707). This
allows the authorization server to return an access token with a scoped
audience, which will then be passed to and validated by the remote MCP server.

By default, ToolHive automatically uses the remote server URL as the resource
indicator when authenticating. The URL is validated, normalized (lowercase
scheme and host, fragments stripped), and included in the OAuth token request.

To explicitly set a different resource indicator, use the
`--remote-auth-resource` flag:
ToolHive does not auto-derive the resource indicator from the server URL by
default. Set it explicitly with `--remote-auth-resource` when the authorization
server requires it:

```bash
thv run https://api.example.com/mcp \
Expand Down
12 changes: 12 additions & 0 deletions docs/toolhive/guides-cli/webhooks.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,16 @@ Use webhooks when your authorization logic is too complex for static Cedar
policies, or when you need to enforce rules managed by an external system (such
as a policy engine or an OPA server).

:::note[Kubernetes equivalent]

In a Kubernetes deployment, configure webhook middleware declaratively with the
[`MCPWebhookConfig`](../reference/crds/mcpwebhookconfig.mdx) custom resource and
reference it from an `MCPServer` via `spec.webhookConfigRef`. The runtime
semantics described below (request and response formats, failure policies,
mutating vs. validating) are identical.

:::

## Prerequisites

- The ToolHive CLI installed. See [Install ToolHive](./install.mdx).
Expand Down Expand Up @@ -241,3 +251,5 @@ which case `patch_type` and `patch` are ignored.
## Related information

- [`thv run` command reference](../reference/cli/thv_run.md)
- [`MCPWebhookConfig` CRD reference](../reference/crds/mcpwebhookconfig.mdx) -
configure webhook middleware declaratively in Kubernetes
50 changes: 43 additions & 7 deletions docs/toolhive/guides-k8s/auth-k8s.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -648,10 +648,19 @@ For a complete walkthrough, see

By default, the embedded authorization server stores sessions in memory.
Upstream tokens are lost when pods restart, requiring users to re-authenticate.
For production deployments, configure Redis Sentinel as the storage backend by
adding a `storage` block to your `MCPExternalAuthConfig`:

```yaml title="storage block for MCPExternalAuthConfig"
For production deployments, configure a Redis backend by adding a `storage`
block to your `MCPExternalAuthConfig`. The `redis` block supports three
connection modes; you must set exactly one:

- **Sentinel** (`sentinelConfig`) - self-managed Redis with Sentinel-based high
availability (HA)
- **Standalone** (`addr` only) - managed Redis services that expose a single
endpoint, such as GCP Memorystore Basic/Standard or Azure Cache for Redis
- **Cluster** (`addr` with `clusterMode: true`) - managed Redis Cluster
services, such as GCP Memorystore Cluster or AWS ElastiCache with cluster mode
enabled

```yaml title="storage block for MCPExternalAuthConfig - Sentinel"
storage:
type: redis
redis:
Expand All @@ -669,17 +678,44 @@ storage:
key: password
```

Create the Secret containing your Redis ACL credentials:
```yaml title="storage block for MCPExternalAuthConfig - Standalone"
storage:
type: redis
redis:
addr: redis.example.com:6379
aclUserConfig:
# usernameSecretRef is optional - omit for managed tiers without ACL
# users (GCP Memorystore Basic/Standard, Azure Cache for Redis)
passwordSecretRef:
name: redis-acl-secret
key: password
```

```yaml title="storage block for MCPExternalAuthConfig - Cluster"
storage:
type: redis
redis:
addr: redis-cluster.example.com:6379
clusterMode: true
aclUserConfig:
passwordSecretRef:
name: redis-acl-secret
key: password
```

Create the Secret containing your Redis credentials. The example below includes
the username for ACL-enabled deployments; omit `--from-literal=username=...`
when targeting a managed tier that does not support ACL users:

```bash
kubectl create secret generic redis-acl-secret \
--namespace toolhive-system \
--from-literal=username=toolhive-auth \
--from-literal=password="YOUR_REDIS_ACL_PASSWORD"
--from-literal=password="YOUR_REDIS_PASSWORD"
```

For a complete walkthrough including deploying Redis Sentinel from scratch, see
[Redis Sentinel session storage](./redis-session-storage.mdx).
[Redis session storage](./redis-session-storage.mdx).

### Using an OAuth 2.0 upstream provider

Expand Down
95 changes: 87 additions & 8 deletions docs/toolhive/guides-k8s/redis-session-storage.mdx
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
---
title: Redis session storage
description:
Deploy Redis Sentinel for the ToolHive embedded auth server, or a standalone
Redis instance for MCPServer and VirtualMCPServer horizontal scaling.
Deploy Redis Sentinel, standalone Redis, or Redis Cluster as the session store
for the ToolHive embedded auth server and horizontal scaling.
---

ToolHive uses Redis for several purposes. This page covers two that require
different configuration:

- **Embedded authorization server sessions** - stores upstream tokens so users
don't need to re-authenticate after pod restarts. Uses Redis Sentinel with
ACL-based authentication and a fixed `thv:auth:*` key pattern. See
don't need to re-authenticate after pod restarts. Supports Sentinel,
standalone, and Redis Cluster connection modes. Uses ACL-based authentication
(optional for managed tiers without ACL users) and a fixed `thv:auth:*` key
pattern. See
[Embedded auth server session storage](#embedded-auth-server-session-storage).

- **MCPServer and VirtualMCPServer horizontal scaling** - shares MCP session
Expand All @@ -31,12 +33,31 @@ You can reuse the same Redis instance for all three purposes by using different

## Embedded auth server session storage

Configure Redis Sentinel as the session storage backend for the ToolHive
Configure Redis as the session storage backend for the ToolHive
[embedded authorization server](../concepts/embedded-auth-server.mdx). By
default, sessions are stored in memory, which means upstream tokens are lost
when pods restart and users must re-authenticate. Redis Sentinel provides
persistent storage with automatic master discovery, ACL-based access control,
and optional failover when replicas are configured.
when pods restart and users must re-authenticate. Redis provides persistent
storage that survives pod restarts.

The `redis` block on `MCPExternalAuthConfig` supports three connection modes,
and exactly one must be set:

- **Sentinel** (`sentinelConfig`) - self-managed Redis with Sentinel-based high
availability (HA), automatic master discovery, and optional failover when
replicas are configured. Covered in the walkthrough below.
- **Standalone** (`addr` only) - a single endpoint, such as a managed Redis
service that handles HA internally (GCP Memorystore Basic/Standard, Azure
Cache for Redis, AWS ElastiCache without cluster mode). See
[Managed Redis: standalone and cluster modes](#managed-redis-standalone-and-cluster-modes).
- **Cluster** (`addr` with `clusterMode: true`) - a single discovery endpoint
for a Redis Cluster service that auto-discovers the full shard topology (GCP
Memorystore Cluster, AWS ElastiCache with cluster mode). See
[Managed Redis: standalone and cluster modes](#managed-redis-standalone-and-cluster-modes).

The rest of this section walks through deploying Redis Sentinel and wiring it
into `MCPExternalAuthConfig`. For managed Redis services, follow the same TLS,
ACL, and verification steps but use the standalone or Cluster `storage` block
instead of `sentinelConfig`.

:::info[Prerequisites]

Expand Down Expand Up @@ -418,6 +439,64 @@ storage:
For the complete list of storage configuration fields, see the
[Kubernetes CRD reference](../reference/crds/mcpexternalauthconfig.mdx#specembeddedauthserverstorage).

### Managed Redis: standalone and cluster modes

Managed Redis services typically expose a single endpoint and handle HA
internally, so the Sentinel walkthrough above does not apply to them. Use one of
the connection modes below instead.

#### Standalone mode

Use `addr` on its own for managed services that present a single, non-clustered
endpoint, such as GCP Memorystore Basic or Standard HA, Azure Cache for Redis,
or AWS ElastiCache without cluster mode enabled:

```yaml title="storage block with standalone addr"
storage:
type: redis
redis:
addr: redis.example.com:6379
aclUserConfig:
# usernameSecretRef is optional - omit on managed tiers that do not
# expose ACL users (GCP Memorystore Basic/Standard HA, Azure Cache
# for Redis). Connections fall back to legacy password-only AUTH.
passwordSecretRef:
name: redis-acl-secret
key: password
```

When `usernameSecretRef` is omitted, ToolHive authenticates with password-only
`AUTH`, which works against managed tiers that lack Redis ACL support. When the
managed service supports ACL users (for example, AWS ElastiCache non-cluster
with Redis 6+ RBAC), set `usernameSecretRef` to use that user.

#### Cluster mode

Set `clusterMode: true` alongside `addr` when the address points at a Redis
Cluster discovery endpoint, such as GCP Memorystore Cluster or AWS ElastiCache
with cluster mode enabled. ToolHive auto-discovers the full shard topology from
the single endpoint:

```yaml title="storage block with Redis Cluster"
storage:
type: redis
redis:
addr: redis-cluster.example.com:6379
clusterMode: true
aclUserConfig:
passwordSecretRef:
name: redis-acl-secret
key: password
```

:::note

`addr` and `sentinelConfig` are mutually exclusive - the CRD admission rules
reject manifests that set both or neither. `clusterMode: true` requires `addr`
and cannot be combined with `sentinelConfig`.

:::

## Enable TLS

Without TLS, Redis credentials and session tokens travel in plaintext between
Expand Down
5 changes: 3 additions & 2 deletions docs/toolhive/guides-vmcp/backend-discovery.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -497,13 +497,14 @@ kubectl get virtualmcpserver engineering-vmcp -n toolhive-system \

**Possible causes and solutions:**

1. **MCPGroup not in Ready state**
1. **MCPGroup hasn't checked its members yet**

```bash
kubectl get mcpgroup my-group -n toolhive-system
```

Wait for the group to reach Ready state before starting vMCP.
Wait for the `Checked` column to report `True` (the `MCPServersChecked`
condition on the MCPGroup status) before starting vMCP.

2. **Backend resources not referencing the correct group**

Expand Down
22 changes: 13 additions & 9 deletions docs/toolhive/guides-vmcp/configuration.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,10 @@ spec:
description: Group of backend MCP servers for vMCP aggregation
```

The MCPGroup must exist in the same namespace as your VirtualMCPServer and be in
a Ready state before the VirtualMCPServer can start. Backend resources reference
this group using the `groupRef` field in their spec.
The MCPGroup must exist in the same namespace as your VirtualMCPServer and have
its `MCPServersChecked` condition reported as `True` before the VirtualMCPServer
can start. Backend resources reference this group using the `groupRef` field in
their spec.

## Add backends to a group

Expand Down Expand Up @@ -130,12 +131,15 @@ spec:
type: anonymous # Disables authentication; do not use in production
```

The MCPGroup must exist in the same namespace and be in a Ready state before the
VirtualMCPServer can start. By default, vMCP automatically discovers and
aggregates all MCPServer, MCPRemoteProxy, and MCPServerEntry resources in the
referenced group. You can also define backends explicitly in the configuration
(inline mode). See [Backend discovery modes](./backend-discovery.mdx) for
details on both approaches.
The MCPGroup must exist in the same namespace, and its `MCPServersChecked`
condition must be `True` before the VirtualMCPServer can start. You can verify
this with `kubectl get mcpgroup` and look for `True` under the `Checked` column.

By default, vMCP automatically discovers and aggregates all MCPServer,
MCPRemoteProxy, and MCPServerEntry resources in the referenced group. You can
also define backends explicitly in the configuration (inline mode). See
[Backend discovery modes](./backend-discovery.mdx) for details on both
approaches.

## Configure authentication

Expand Down
5 changes: 3 additions & 2 deletions docs/toolhive/guides-vmcp/quickstart.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -269,8 +269,9 @@ backends into a single endpoint.
<details>
<summary>VirtualMCPServer stuck in Pending</summary>

The vMCP pod won't become Ready until the MCPGroup is in `Ready` state and at
least one backend MCPServer is `Ready`. Check both:
The vMCP pod won't become Ready until the MCPGroup's `Checked` column reports
`True` (its `MCPServersChecked` condition) and at least one backend MCPServer is
`Ready`. Check both:

```bash
kubectl get mcpgroups,mcpservers -n toolhive-system
Expand Down
1 change: 1 addition & 0 deletions docs/toolhive/reference/cli/thv.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ thv [flags]
* [thv group](thv_group.md) - Manage logical groupings of MCP servers
* [thv inspector](thv_inspector.md) - Launches the MCP Inspector UI and connects it to the specified MCP server
* [thv list](thv_list.md) - List running MCP servers
* [thv llm](thv_llm.md) - Manage LLM gateway authentication
* [thv logs](thv_logs.md) - Output the logs of an MCP server or manage log files
* [thv mcp](thv_mcp.md) - Interact with MCP servers for debugging
* [thv proxy](thv_proxy.md) - Create a transparent proxy for an MCP server with authentication support
Expand Down
Loading