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.52.0
version: v0.53.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
5 changes: 4 additions & 1 deletion docs/toolhive/faq.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,10 @@ Yes. ToolHive uses permission profiles to control:
- **Network access** - which hosts and ports the server can connect to

You can use built-in profiles or create custom ones for specific security
requirements.
requirements. Permission profiles apply to servers you run with the ToolHive CLI
and UI. In Kubernetes, use pod security contexts and `NetworkPolicy` resources
instead; see
[Restrict file system and network access](./guides-k8s/run-mcp-k8s.mdx#restrict-file-system-and-network-access).

### What's network isolation and when should I use it?

Expand Down
5 changes: 5 additions & 0 deletions docs/toolhive/guides-cli/run-mcp-servers.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -555,6 +555,11 @@ these separately before applying the manifest:
- **Telemetry**: create an `MCPTelemetryConfig` resource and add a
`telemetryConfigRef` to the `MCPServer`.

The exported manifest also omits the server's permission profile, because the
operator doesn't apply permission profiles to Kubernetes workloads. To restrict
file system and network access in the cluster, see
[Restrict file system and network access](../guides-k8s/run-mcp-k8s.mdx#restrict-file-system-and-network-access).

`thv export` doesn't support exporting remote MCP servers to Kubernetes. To run
a remote server with the operator, configure an
[`MCPRemoteProxy`](../guides-k8s/remote-mcp-proxy.mdx) resource directly.
Expand Down
5 changes: 5 additions & 0 deletions docs/toolhive/guides-k8s/remote-mcp-proxy.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -906,6 +906,11 @@ kubectl logs -n toolhive-system -l app.kubernetes.io/instance=analytics-proxy
The status shows:

- **Phase**: Current state (Pending, Ready, Failed, Terminating)
- **Message**: A description of the current state. If the operator can't resolve
a referenced configuration resource, such as an `MCPOIDCConfig` or
`MCPTelemetryConfig`, the phase moves to `Failed`, the `Ready` condition is
set to `False`, and the message names the failing resource (for example,
`Failed to handle MCPOIDCConfig: ...`).
- **URL**: Internal cluster URL
- **External URL**: External URL if exposed via Ingress
- **Conditions**: Detailed status conditions, including `PodTemplateValid` when
Expand Down
14 changes: 14 additions & 0 deletions docs/toolhive/guides-k8s/run-mcp-k8s.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,20 @@ process.

:::

#### Restrict file system and network access

The operator doesn't apply ToolHive permission profiles to Kubernetes workloads.
To restrict what an MCP server can access, use standard Kubernetes controls: set
a `securityContext` on the `mcp` container in `podTemplateSpec`, mount only the
volumes the server needs, and limit network traffic with
[`NetworkPolicy`](https://kubernetes.io/docs/concepts/services-networking/network-policies/)
resources.

The `spec.permissionProfile` field is deprecated and has no effect. It remains
in the CRD so existing manifests still apply, but the operator records a
`PermissionProfileUnsupported` Warning event on any MCPServer that sets it.
Remove the field from your manifests to clear the warning.

### Run a server with secrets

When your MCP servers require authentication tokens or other secrets, ToolHive
Expand Down
10 changes: 10 additions & 0 deletions docs/toolhive/guides-vmcp/quickstart.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -321,4 +321,14 @@ If the backend exposes a tool that the vMCP doesn't, check your tool aggregation
configuration for filters that might be excluding it. See
[Tool aggregation](./tool-aggregation.mdx) for filter and override rules.

If no filter explains the gap, check the vMCP logs. When vMCP can't decode an
individual tool, resource, resource template, or prompt that a backend
advertises, it can skip that item and keep the backend's other capabilities.
Look for a `skipping undecodable item` warning that names the backend and the
item:

```bash
kubectl logs -n toolhive-system deployment/demo-vmcp | grep "skipping undecodable item"
```

</details>
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.52.0`. Source:
[`values.yaml`](https://github.com/stacklok/toolhive/blob/v0.52.0/deploy/charts/operator-crds/values.yaml)
at `v0.52.0`.
Chart: `toolhive-operator-crds`. Chart version: `0.53.0`. Source:
[`values.yaml`](https://github.com/stacklok/toolhive/blob/v0.53.0/deploy/charts/operator-crds/values.yaml)
at `v0.53.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.52.0`. Source:
[`values.yaml`](https://github.com/stacklok/toolhive/blob/v0.52.0/deploy/charts/operator/values.yaml)
at `v0.52.0`.
Chart: `toolhive-operator`. Chart version: `0.53.0`. Source:
[`values.yaml`](https://github.com/stacklok/toolhive/blob/v0.53.0/deploy/charts/operator/values.yaml)
at `v0.53.0`.

## Values

Expand Down Expand Up @@ -68,7 +68,7 @@ features:
gc:
gogc: 75
gomemlimit: 110MiB
image: ghcr.io/stacklok/toolhive/operator:v0.52.0
image: ghcr.io/stacklok/toolhive/operator:v0.53.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.52.0
vmcpImage: ghcr.io/stacklok/toolhive/vmcp:v0.52.0
toolhiveRunnerImage: ghcr.io/stacklok/toolhive/proxyrunner:v0.53.0
vmcpImage: ghcr.io/stacklok/toolhive/vmcp:v0.53.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.52.0"`
**Default:** `"ghcr.io/stacklok/toolhive/operator:v0.53.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.52.0"`
**Default:** `"ghcr.io/stacklok/toolhive/proxyrunner:v0.53.0"`

Container image for ToolHive proxy runners created by the operator.

### `operator.vmcpImage`

**Type:** `string`

**Default:** `"ghcr.io/stacklok/toolhive/vmcp:v0.52.0"`
**Default:** `"ghcr.io/stacklok/toolhive/vmcp:v0.53.0"`

Container image for Virtual MCP Server (vMCP) deployments created by the
operator.
Expand Down
77 changes: 77 additions & 0 deletions static/api-specs/toolhive-api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -744,6 +744,8 @@ components:
$ref: '#/components/schemas/tokenexchange.TrustedIssuer'
type: array
uniqueItems: false
upstream_filter:
$ref: '#/components/schemas/upstreamfilter.GroupBasedFilterConfig'
upstreams:
description: |-
Upstreams configures connections to upstream Identity Providers for
Expand Down Expand Up @@ -5499,6 +5501,81 @@ components:
shared:
$ref: '#/components/schemas/types.RateLimitBucket'
type: object
upstreamfilter.GroupBasedFilterConfig:
description: |-
UpstreamFilter configures an optional group-based filter that narrows the
multi-upstream authorization chain per-request based on the first
upstream's resolved identity. When nil, every request walks the full
upstream chain in configured order (preserving behavior from before the
filter hook existed).

The filter is applied exactly once per authorization, on the first leg's
callback. The mandatory first upstream (Upstreams[0]) is never filtered
out. See pkg/authserver/upstreamfilter for the configuration schema and
pkg/authserver/server/handlers for the runtime contract.
properties:
claim:
description: |-
Claim is the exact, top-level JWT/OIDC claim name to read group
membership from. Defaults to "groups" when empty. The claim value MUST be
a JSON array of strings; other shapes (missing, non-array, mixed types)
fail the authorization. Nested claims (e.g. Keycloak's realm_access.roles)
must be flattened by the identity provider — nested paths are not
supported in this implementation.

The value is read from upstream.Identity.Claims as the first upstream
resolves it: an OIDC upstream fills that map from the validated ID
token, a pure OAuth2 upstream from the userinfo response. The
identityFromToken and synthesized-identity paths leave it empty, and
groups carried only in an access token never reach it. A missing claim
fails the authorization — DefaultUpstreams covers a valid-but-empty
claim and unmatched groups, not an absent one.
type: string
defaultUpstreams:
description: |-
DefaultUpstreams is used when the claim is a valid empty array or when no
rule matches. When empty or omitted, no optional upstreams are added —
the mandatory first upstream remains but the authorization walks no
further.
items:
type: string
type: array
uniqueItems: false
rules:
description: |-
Rules assigns groups to upstream provider subsets. A principal matches a
rule if any of the principal's group values appears in the rule's Groups
list (any-of / OR semantics). Multiple matching rules are combined by
union: the effective upstream chain contains every UpstreamProviders
value from every matching rule, deduplicated.
items:
$ref: '#/components/schemas/upstreamfilter.GroupRule'
type: array
uniqueItems: false
type: object
upstreamfilter.GroupRule:
properties:
groups:
description: |-
Groups is the set of principal group names this rule matches. Matching
is case-sensitive and exact. Empty and duplicate values are rejected at
configuration validation time.
items:
type: string
type: array
uniqueItems: false
upstreamProviders:
description: |-
UpstreamProviders lists the non-first upstream provider names to include
in the chain when this rule matches. All names must reference a
configured non-first upstream. Empty and duplicate values, references to
the mandatory first upstream, and references to undeclared upstreams are
rejected at configuration validation time.
items:
type: string
type: array
uniqueItems: false
type: object
v0.ServerJSON:
properties:
$schema:
Expand Down
2 changes: 1 addition & 1 deletion static/api-specs/toolhive-crds/mcpservers.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@
"type": "object"
},
"permissionProfile": {
"description": "PermissionProfile defines the permission profile to use",
"description": "PermissionProfile is deprecated and retained for API compatibility. It is\nnot supported by the Kubernetes operator and has no effect.",
"properties": {
"key": {
"description": "Key is the key in the ConfigMap that contains the permission profile\nOnly used when Type is \"configmap\"",
Expand Down
Loading