Skip to content

Commit 2abd168

Browse files
rdimitrovclaude
andcommitted
Update docs for ToolHive v0.12.3–v0.13.0
Catch up documentation with features shipped in v0.12.3 through v0.13.0. Auto-generated CLI/CRD reference docs were already current; these changes cover manual doc updates verified against source code at each release tag. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 2f5051f commit 2abd168

7 files changed

Lines changed: 221 additions & 39 deletions

File tree

docs/toolhive/concepts/backend-auth.mdx

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -211,18 +211,23 @@ deployments using the ToolHive Operator.
211211
- **Direct upstream redirect:** The embedded authorization server redirects
212212
clients directly to the upstream provider for authentication (for example,
213213
GitHub or Atlassian).
214-
- **Single upstream provider:** Currently supports one upstream identity
215-
provider per configuration.
216-
217-
:::info[Chained authentication not yet supported]
218-
219-
The embedded authorization server redirects clients directly to the upstream
220-
provider. This means the upstream provider must be the service whose API the MCP
221-
server calls. Chained authentication—where a client authenticates with a
214+
- **Multiple upstream providers (VirtualMCPServer):** VirtualMCPServer supports
215+
configuring multiple upstream identity providers with sequential
216+
authentication. When multiple providers are configured, the authorization
217+
server chains the authentication flow through each provider in sequence,
218+
collecting tokens from all of them. This enables scenarios where backend tools
219+
require tokens from different providers (such as a corporate IdP and GitHub).
220+
221+
:::info[Chained authentication for MCPServer]
222+
223+
MCPServer and MCPRemoteProxy support only one upstream provider. The embedded
224+
authorization server redirects clients directly to that provider, so the
225+
provider must be the service whose API the MCP server calls. If your MCPServer
226+
deployment requires chained authentication—where a client authenticates with a
222227
corporate IdP like Okta, which then federates to an external provider like
223-
GitHub—is not yet supported. If your deployment requires this pattern, consider
224-
using [token exchange](#same-idp-with-token-exchange) with a federated identity
225-
provider instead.
228+
GitHub—consider using [token exchange](#same-idp-with-token-exchange) with a
229+
federated identity provider instead, or use a VirtualMCPServer with multiple
230+
upstream providers.
226231

227232
:::
228233

docs/toolhive/guides-k8s/auth-k8s.mdx

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -464,13 +464,13 @@ kubectl apply -f embedded-auth-config.yaml
464464

465465
**Configuration reference:**
466466

467-
| Field | Description |
468-
| ---------------------- | ---------------------------------------------------------------------------------------------------------------------- |
469-
| `issuer` | HTTPS URL identifying this authorization server. Appears in the `iss` claim of issued JWTs. |
470-
| `signingKeySecretRefs` | References to Secrets containing JWT signing keys. First key is active; additional keys support rotation. |
471-
| `hmacSecretRefs` | References to Secrets with symmetric keys for signing authorization codes and refresh tokens. |
472-
| `tokenLifespans` | Configurable durations for access tokens (default: 1h), refresh tokens (default: 168h), and auth codes (default: 10m). |
473-
| `upstreamProviders` | Configuration for the upstream identity provider. Currently supports one provider. |
467+
| Field | Description |
468+
| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
469+
| `issuer` | HTTPS URL identifying this authorization server. Appears in the `iss` claim of issued JWTs. |
470+
| `signingKeySecretRefs` | References to Secrets containing JWT signing keys. First key is active; additional keys support rotation. |
471+
| `hmacSecretRefs` | References to Secrets with symmetric keys for signing authorization codes and refresh tokens. |
472+
| `tokenLifespans` | Configurable durations for access tokens (default: 1h), refresh tokens (default: 168h), and auth codes (default: 10m). |
473+
| `upstreamProviders` | Configuration for upstream identity providers. MCPServer and MCPRemoteProxy support one provider; VirtualMCPServer supports multiple providers for sequential authentication. |
474474

475475
**Step 5: Create the MCPServer resource**
476476

docs/toolhive/guides-k8s/redis-session-storage.mdx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
title: Redis Sentinel session storage
33
description:
44
How to deploy Redis Sentinel and configure persistent session storage for the
5-
ToolHive embedded authorization server.
5+
ToolHive embedded authorization server and horizontal scaling.
66
---
77

88
Deploy Redis Sentinel and configure it as the session storage backend for the
@@ -12,6 +12,11 @@ re-authenticate. Redis Sentinel provides persistent storage with automatic
1212
master discovery, ACL-based access control, and optional failover when replicas
1313
are configured.
1414

15+
Redis session storage is also required for horizontal scaling when running
16+
multiple [MCPServer](./run-mcp-k8s.mdx#horizontal-scaling) or
17+
[VirtualMCPServer](../guides-vmcp/scaling-and-performance.mdx#session-storage-for-multi-replica-deployments)
18+
replicas, so that sessions are shared across pods.
19+
1520
:::info[Prerequisites]
1621

1722
Before you begin, ensure you have:

docs/toolhive/guides-k8s/run-mcp-k8s.mdx

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -439,6 +439,41 @@ For more details about a specific MCP server:
439439
kubectl -n <NAMESPACE> describe mcpserver <NAME>
440440
```
441441

442+
## Horizontal scaling
443+
444+
MCPServer creates two separate Deployments: one for the proxy runner and one for
445+
the MCP server backend. You can scale each independently:
446+
447+
- `spec.replicas` controls the proxy runner pod count
448+
- `spec.backendReplicas` controls the backend MCP server pod count
449+
450+
```yaml title="MCPServer resource"
451+
spec:
452+
replicas: 2
453+
backendReplicas: 3
454+
sessionStorage:
455+
provider: redis
456+
address: redis-master.toolhive-system.svc.cluster.local:6379
457+
db: 0
458+
keyPrefix: mcp-sessions
459+
passwordRef:
460+
name: redis-secret
461+
key: password
462+
```
463+
464+
When running multiple replicas, configure
465+
[Redis session storage](./redis-session-storage.mdx) so that sessions are shared
466+
across pods. If you omit `replicas` or `backendReplicas`, the operator defers
467+
replica management to an HPA or other external controller.
468+
469+
:::warning[Stdio transport limitation]
470+
471+
Backends using the `stdio` transport are limited to a single replica. The
472+
operator rejects configurations with `backendReplicas` greater than 1 for stdio
473+
backends.
474+
475+
:::
476+
442477
## Next steps
443478

444479
- [Connect clients to your MCP servers](./connect-clients.mdx) from outside the
@@ -455,6 +490,8 @@ kubectl -n <NAMESPACE> describe mcpserver <NAME>
455490

456491
- [Kubernetes CRD reference](../reference/crd-spec.md#apiv1alpha1mcpserver) -
457492
Reference for the `MCPServer` Custom Resource Definition (CRD)
493+
- [vMCP scaling and performance](../guides-vmcp/scaling-and-performance.mdx) -
494+
Scale Virtual MCP Server deployments
458495
- [Deploy the operator](./deploy-operator.mdx) - Install the ToolHive operator
459496
- [Build MCP containers](../guides-cli/build-containers.mdx) - Create custom MCP
460497
server container images

docs/toolhive/guides-vmcp/composite-tools.mdx

Lines changed: 68 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ backend MCP servers, handling dependencies and collecting results.
1919
wait for their prerequisites
2020
- **Template expansion**: Dynamic arguments using step outputs
2121
- **Elicitation**: Request user input mid-workflow (approval gates, choices)
22+
- **Iteration**: Loop over collections with forEach steps
2223
- **Error handling**: Configurable abort, continue, or retry behavior
2324
- **Timeouts**: Workflow and per-step timeout configuration
2425

@@ -290,7 +291,7 @@ spec:
290291

291292
### Steps
292293

293-
Each step can be a tool call or an elicitation:
294+
Each step can be a tool call, an elicitation, or a forEach loop:
294295

295296
```yaml title="VirtualMCPServer resource"
296297
spec:
@@ -344,6 +345,62 @@ spec:
344345
timeout: '5m'
345346
```
346347

348+
### forEach steps
349+
350+
Iterate over a collection from a previous step's output and execute a tool call
351+
for each item:
352+
353+
```yaml title="VirtualMCPServer resource"
354+
spec:
355+
config:
356+
compositeTools:
357+
- name: scan_repositories
358+
description: Check each repository for security advisories
359+
parameters:
360+
type: object
361+
properties:
362+
org:
363+
type: string
364+
required:
365+
- org
366+
steps:
367+
- id: list_repos
368+
tool: github_list_repos
369+
arguments:
370+
org: '{{.params.org}}'
371+
# highlight-start
372+
- id: check_advisories
373+
type: forEach
374+
collection: '{{json .steps.list_repos.output.repositories}}'
375+
itemVar: repo
376+
maxParallel: 5
377+
step:
378+
type: tool
379+
tool: github_list_security_advisories
380+
arguments:
381+
repo: '{{.forEach.repo.name}}'
382+
onError:
383+
action: continue
384+
dependsOn: [list_repos]
385+
# highlight-end
386+
```
387+
388+
**forEach fields:**
389+
390+
| Field | Description | Default |
391+
| --------------- | ----------------------------------------------------- | ------- |
392+
| `collection` | Template expression that produces an array | — |
393+
| `itemVar` | Variable name for the current item | item |
394+
| `maxParallel` | Maximum concurrent iterations (max 50) | 10 |
395+
| `maxIterations` | Maximum total iterations (max 1000) | 100 |
396+
| `step` | Inner step definition (tool call to execute per item) | — |
397+
| `onError` | Error handling: `abort` (stop) or `continue` (skip) | abort |
398+
399+
Access the current item inside the inner step using
400+
`{{.forEach.<itemVar>.<field>}}`. In the example above, `{{.forEach.repo.name}}`
401+
accesses the `name` field of the current repository. You can also use
402+
`{{.forEach.index}}` to access the zero-based iteration index.
403+
347404
### Error handling
348405

349406
Configure behavior when steps fail:
@@ -507,13 +564,16 @@ without defaultResults defined
507564

508565
Access workflow context in arguments:
509566

510-
| Template | Description |
511-
| --------------------------- | ------------------------------------------ |
512-
| `{{.params.name}}` | Input parameter |
513-
| `{{.steps.id.output}}` | Step output (map) |
514-
| `{{.steps.id.output.text}}` | Text content from step output |
515-
| `{{.steps.id.content}}` | Elicitation response content |
516-
| `{{.steps.id.action}}` | Elicitation action (accept/decline/cancel) |
567+
| Template | Description |
568+
| -------------------------------- | ------------------------------------------ |
569+
| `{{.params.name}}` | Input parameter |
570+
| `{{.steps.id.output}}` | Step output (map) |
571+
| `{{.steps.id.output.text}}` | Text content from step output |
572+
| `{{.steps.id.content}}` | Elicitation response content |
573+
| `{{.steps.id.action}}` | Elicitation action (accept/decline/cancel) |
574+
| `{{.forEach.<itemVar>}}` | Current forEach item |
575+
| `{{.forEach.<itemVar>.<field>}}` | Field on current forEach item |
576+
| `{{.forEach.index}}` | Zero-based iteration index |
517577

518578
### Template functions
519579

docs/toolhive/guides-vmcp/scaling-and-performance.mdx

Lines changed: 51 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@ description:
44
How to scale Virtual MCP Server deployments vertically and horizontally.
55
---
66

7-
This guide explains how to scale Virtual MCP Server (vMCP) deployments.
7+
This guide explains how to scale Virtual MCP Server (vMCP) deployments. For
8+
MCPServer scaling, see
9+
[Horizontal scaling](../guides-k8s/run-mcp-k8s.mdx#horizontal-scaling) in the
10+
Kubernetes operator guide.
811

912
## Vertical scaling
1013

@@ -37,24 +40,60 @@ higher request volumes.
3740
3841
### How to scale horizontally
3942
40-
The VirtualMCPServer CRD does not have a `replicas` field. The operator creates
41-
a Deployment named `vmcp-<NAME>` (where `<NAME>` is your VirtualMCPServer name)
42-
with 1 replica and preserves the replicas count, allowing you to manage scaling
43-
separately.
43+
Set the `replicas` field in your VirtualMCPServer spec to control the number of
44+
vMCP pods:
45+
46+
```yaml title="VirtualMCPServer resource"
47+
spec:
48+
replicas: 3
49+
```
50+
51+
If you omit `replicas`, the operator defers replica management to an HPA or
52+
other external controller. You can also scale manually or with an HPA:
4453

4554
**Option 1: Manual scaling**
4655

4756
```bash
48-
kubectl scale deployment vmcp-<vmcp-name> -n <NAMESPACE> --replicas=3
57+
kubectl scale deployment vmcp-<VMCP_NAME> -n <NAMESPACE> --replicas=3
4958
```
5059

5160
**Option 2: Autoscaling with HPA**
5261

5362
```bash
54-
kubectl autoscale deployment vmcp-<vmcp-name> -n <NAMESPACE> \
63+
kubectl autoscale deployment vmcp-<VMCP_NAME> -n <NAMESPACE> \
5564
--min=2 --max=5 --cpu-percent=70
5665
```
5766

67+
### Session storage for multi-replica deployments
68+
69+
When running multiple replicas, configure Redis session storage so that sessions
70+
are shared across pods. Without session storage, a request routed to a different
71+
replica than the one that established the session will fail.
72+
73+
```yaml title="VirtualMCPServer resource"
74+
spec:
75+
replicas: 3
76+
sessionStorage:
77+
provider: redis
78+
address: redis-master.toolhive-system.svc.cluster.local:6379
79+
db: 0
80+
keyPrefix: vmcp-sessions
81+
passwordRef:
82+
name: redis-secret
83+
key: password
84+
```
85+
86+
See [Redis Sentinel session storage](../guides-k8s/redis-session-storage.mdx)
87+
for a complete Redis deployment guide.
88+
89+
:::warning
90+
91+
If you configure multiple replicas without session storage, the operator sets a
92+
`SessionStorageMissingForReplicas` status condition on the resource. Ensure
93+
Redis is available before scaling beyond a single replica.
94+
95+
:::
96+
5897
### When horizontal scaling is challenging
5998

6099
Horizontal scaling works well for **stateless backends** (fetch, search,
@@ -63,22 +102,22 @@ read-only operations) where sessions can be resumed on any instance.
63102
However, **stateful backends** make horizontal scaling difficult:
64103

65104
- **Stateful backends** (Playwright browser sessions, database connections, file
66-
system operations) require requests to be routed to the same vMCP instance
67-
that established the session
105+
system operations) require requests to be routed to the same instance that
106+
established the session
68107
- Session resumption may not work reliably for stateful backends
69108

70109
The `VirtualMCPServer` CRD includes a `sessionAffinity` field that controls how
71110
the Kubernetes Service routes repeated client connections. By default, it uses
72111
`ClientIP` affinity, which routes connections from the same client IP to the
73-
same pod. You can configure this using the `sessionAffinity` field:
112+
same pod:
74113

75114
```yaml
76115
spec:
77116
sessionAffinity: ClientIP # default
78117
```
79118

80-
For stateful backends, vertical scaling or dedicated vMCP instances per team/use
81-
case are recommended instead of horizontal scaling.
119+
For stateful backends, vertical scaling or dedicated instances per team/use case
120+
are recommended instead of horizontal scaling.
82121

83122
## Next steps
84123

docs/toolhive/guides-vmcp/tool-aggregation.mdx

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,42 @@ spec:
146146
description: 'Create a new GitHub issue in the repository'
147147
```
148148

149+
### Annotation overrides
150+
151+
Override MCP tool annotations to provide hints to LLM clients about tool
152+
behavior. Annotations are optional—only set the fields you want to override:
153+
154+
```yaml title="VirtualMCPServer resource"
155+
spec:
156+
config:
157+
aggregation:
158+
tools:
159+
- workload: github
160+
overrides:
161+
delete_repository:
162+
annotations:
163+
destructiveHint: true
164+
readOnlyHint: false
165+
list_issues:
166+
annotations:
167+
title: 'List GitHub Issues'
168+
readOnlyHint: true
169+
idempotentHint: true
170+
```
171+
172+
**Available annotation fields:**
173+
174+
| Field | Type | Description |
175+
| ----------------- | ------- | -------------------------------------------------- |
176+
| `title` | string | Display title for the tool in MCP clients |
177+
| `readOnlyHint` | boolean | Indicates the tool does not modify data |
178+
| `destructiveHint` | boolean | Indicates the tool may delete or overwrite data |
179+
| `idempotentHint` | boolean | Indicates repeated calls produce the same result |
180+
| `openWorldHint` | boolean | Indicates the tool interacts with external systems |
181+
182+
Annotation overrides can be combined with name and description overrides on the
183+
same tool.
184+
149185
:::info
150186

151187
You can also reference an `MCPToolConfig` resource using `toolConfigRef` instead

0 commit comments

Comments
 (0)