diff --git a/.github/upstream-projects.yaml b/.github/upstream-projects.yaml index 926f09ed..6f8cfacd 100644 --- a/.github/upstream-projects.yaml +++ b/.github/upstream-projects.yaml @@ -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/, diff --git a/docs/toolhive/faq.mdx b/docs/toolhive/faq.mdx index 0d1ee443..53c73585 100644 --- a/docs/toolhive/faq.mdx +++ b/docs/toolhive/faq.mdx @@ -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? diff --git a/docs/toolhive/guides-cli/run-mcp-servers.mdx b/docs/toolhive/guides-cli/run-mcp-servers.mdx index b4549120..1ce3a102 100644 --- a/docs/toolhive/guides-cli/run-mcp-servers.mdx +++ b/docs/toolhive/guides-cli/run-mcp-servers.mdx @@ -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. diff --git a/docs/toolhive/guides-k8s/remote-mcp-proxy.mdx b/docs/toolhive/guides-k8s/remote-mcp-proxy.mdx index 7b08ee3f..060a47e2 100644 --- a/docs/toolhive/guides-k8s/remote-mcp-proxy.mdx +++ b/docs/toolhive/guides-k8s/remote-mcp-proxy.mdx @@ -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 diff --git a/docs/toolhive/guides-k8s/run-mcp-k8s.mdx b/docs/toolhive/guides-k8s/run-mcp-k8s.mdx index 0c418d8f..20c90bb9 100644 --- a/docs/toolhive/guides-k8s/run-mcp-k8s.mdx +++ b/docs/toolhive/guides-k8s/run-mcp-k8s.mdx @@ -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 diff --git a/docs/toolhive/guides-vmcp/quickstart.mdx b/docs/toolhive/guides-vmcp/quickstart.mdx index ea675773..edee8835 100644 --- a/docs/toolhive/guides-vmcp/quickstart.mdx +++ b/docs/toolhive/guides-vmcp/quickstart.mdx @@ -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" +``` + diff --git a/docs/toolhive/reference/helm/operator-crds.mdx b/docs/toolhive/reference/helm/operator-crds.mdx index c7b50b06..4622f3fd 100644 --- a/docs/toolhive/reference/helm/operator-crds.mdx +++ b/docs/toolhive/reference/helm/operator-crds.mdx @@ -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 diff --git a/docs/toolhive/reference/helm/operator.mdx b/docs/toolhive/reference/helm/operator.mdx index cb983acb..3886c22b 100644 --- a/docs/toolhive/reference/helm/operator.mdx +++ b/docs/toolhive/reference/helm/operator.mdx @@ -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 @@ -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: {} @@ -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: [] ``` @@ -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. @@ -271,7 +271,7 @@ 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. @@ -279,7 +279,7 @@ Container image for ToolHive proxy runners created by the operator. **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. diff --git a/static/api-specs/toolhive-api.yaml b/static/api-specs/toolhive-api.yaml index 0c4e5dd0..3f28ec76 100644 --- a/static/api-specs/toolhive-api.yaml +++ b/static/api-specs/toolhive-api.yaml @@ -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 @@ -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: diff --git a/static/api-specs/toolhive-crds/mcpservers.schema.json b/static/api-specs/toolhive-crds/mcpservers.schema.json index 827ab451..35ee2a4a 100644 --- a/static/api-specs/toolhive-crds/mcpservers.schema.json +++ b/static/api-specs/toolhive-crds/mcpservers.schema.json @@ -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\"",