From e0ed5ebaca945021d066f0af63a6106cecc94c2e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 23 Sep 2026 00:08:18 +0000 Subject: [PATCH 1/4] Update stacklok/toolhive to v0.51.1 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/upstream-projects.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/upstream-projects.yaml b/.github/upstream-projects.yaml index e597dd10..774b72a0 100644 --- a/.github/upstream-projects.yaml +++ b/.github/upstream-projects.yaml @@ -44,7 +44,7 @@ projects: - id: toolhive repo: stacklok/toolhive - version: v0.50.0 + version: v0.51.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/, From a941e9258654d5c092e2d330b69cbab82f15e5de Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 23 Sep 2026 00:09:24 +0000 Subject: [PATCH 2/4] Refresh reference assets for toolhive v0.51.1 --- docs/toolhive/reference/crds/mcpgroup.mdx | 2 +- docs/toolhive/reference/crds/mcpserver.mdx | 2 +- static/api-specs/toolhive-crds/index.json | 8 ++------ static/api-specs/toolhive-crds/mcpgroups.schema.json | 3 +-- static/api-specs/toolhive-crds/mcpservers.schema.json | 5 +---- 5 files changed, 6 insertions(+), 14 deletions(-) diff --git a/docs/toolhive/reference/crds/mcpgroup.mdx b/docs/toolhive/reference/crds/mcpgroup.mdx index f34c9ab3..f2f79be6 100644 --- a/docs/toolhive/reference/crds/mcpgroup.mdx +++ b/docs/toolhive/reference/crds/mcpgroup.mdx @@ -8,7 +8,7 @@ toc_max_heading_level: 4 `MCPGroup` is a grouping construct for backend workloads. Other resources reference an `MCPGroup` by name to join a shared pool - for example, a [VirtualMCPServer](./virtualmcpserver.mdx) aggregates the tools exposed by every member of its referenced group. **API:** `toolhive.stacklok.dev/v1beta1` - · **Scope:** Namespaced · **Short names:** `mcpg`, `mcpgroup` + · **Scope:** Namespaced · **Short names:** `mcpg` ## Example diff --git a/docs/toolhive/reference/crds/mcpserver.mdx b/docs/toolhive/reference/crds/mcpserver.mdx index 028e46aa..4ff01188 100644 --- a/docs/toolhive/reference/crds/mcpserver.mdx +++ b/docs/toolhive/reference/crds/mcpserver.mdx @@ -8,7 +8,7 @@ toc_max_heading_level: 4 `MCPServer` defines a containerized MCP server managed by the ToolHive Kubernetes operator. The operator watches `MCPServer` resources and reconciles them into a running, proxied MCP server with the configured transport, authentication, telemetry, and tool filtering. **API:** `toolhive.stacklok.dev/v1beta1` - · **Scope:** Namespaced · **Short names:** `mcpserver`, `mcpservers` + · **Scope:** Namespaced ## Example diff --git a/static/api-specs/toolhive-crds/index.json b/static/api-specs/toolhive-crds/index.json index 64291ef7..2f209ae0 100644 --- a/static/api-specs/toolhive-crds/index.json +++ b/static/api-specs/toolhive-crds/index.json @@ -104,8 +104,7 @@ "group": "toolhive.stacklok.dev", "version": "v1beta1", "shortNames": [ - "mcpg", - "mcpgroup" + "mcpg" ], "scope": "Namespaced", "description": "MCPGroup is the Schema for the mcpgroups API", @@ -272,10 +271,7 @@ "plural": "mcpservers", "group": "toolhive.stacklok.dev", "version": "v1beta1", - "shortNames": [ - "mcpserver", - "mcpservers" - ], + "shortNames": [], "scope": "Namespaced", "description": "MCPServer is the Schema for the mcpservers API", "references": [ diff --git a/static/api-specs/toolhive-crds/mcpgroups.schema.json b/static/api-specs/toolhive-crds/mcpgroups.schema.json index e827b3a1..999105a6 100644 --- a/static/api-specs/toolhive-crds/mcpgroups.schema.json +++ b/static/api-specs/toolhive-crds/mcpgroups.schema.json @@ -7,8 +7,7 @@ "x-kubernetes-version": "v1beta1", "x-kubernetes-plural": "mcpgroups", "x-kubernetes-short-names": [ - "mcpg", - "mcpgroup" + "mcpg" ], "x-kubernetes-scope": "Namespaced", "properties": { diff --git a/static/api-specs/toolhive-crds/mcpservers.schema.json b/static/api-specs/toolhive-crds/mcpservers.schema.json index ee1e0d72..c74f1bdb 100644 --- a/static/api-specs/toolhive-crds/mcpservers.schema.json +++ b/static/api-specs/toolhive-crds/mcpservers.schema.json @@ -6,10 +6,7 @@ "x-kubernetes-kind": "MCPServer", "x-kubernetes-version": "v1beta1", "x-kubernetes-plural": "mcpservers", - "x-kubernetes-short-names": [ - "mcpserver", - "mcpservers" - ], + "x-kubernetes-short-names": [], "x-kubernetes-scope": "Namespaced", "properties": { "spec": { From f691ff64314682babaa681cab65bc2c5bb8bb1a6 Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Wed, 23 Sep 2026 00:15:47 +0000 Subject: [PATCH 3/4] Document derived authz for completion and subscriptions completion/complete and subscriptions/listen are no longer always-allowed in v0.51.0; both are authorized against the prompt or resource they reference. Update the Cedar actions section to describe the derived checks, the request-shape rejections, and the upgrade action for existing deployments. Co-Authored-By: Claude Opus 4.7 --- .../reference/authz-policy-reference.mdx | 83 +++++++++++++++++-- 1 file changed, 74 insertions(+), 9 deletions(-) diff --git a/docs/toolhive/reference/authz-policy-reference.mdx b/docs/toolhive/reference/authz-policy-reference.mdx index 24ddc93e..a8423c2c 100644 --- a/docs/toolhive/reference/authz-policy-reference.mdx +++ b/docs/toolhive/reference/authz-policy-reference.mdx @@ -46,6 +46,72 @@ These actions are evaluated against your Cedar policies: | `Action::"read_resource"` | `resources/read` | Read a specific data resource | | `Action::"get_skill"` | `skills/get` | Retrieve a specific skill | +Several other MCP methods name a capability the same actions already govern. +They are authorized as that capability rather than through actions of their own, +so one policy covers the direct method and every method that references it: + +| MCP method | Authorized as | +| ---------------------------------------------- | ------------------------------------------------------------------------- | +| `resources/subscribe`, `resources/unsubscribe` | `read_resource` on the URI in the request | +| `completion/complete` with a `ref/prompt` | `get_prompt` on the referenced prompt name | +| `completion/complete` with a `ref/resource` | `read_resource` on the referenced URI or URI template | +| `subscriptions/listen` | `read_resource` on **every** URI in `notifications.resourceSubscriptions` | + +For a resource template, the entity ID is the template string itself. A policy +naming only concrete URIs does not cover completion for the template: + +```text +// Allows reading the resource, but NOT completions on the template. +permit( + principal, + action == Action::"read_resource", + resource == Resource::"secrets://tenant/admin" +); + +// Required as well, to allow completing the template's {name} variable. +permit( + principal, + action == Action::"read_resource", + resource == Resource::"secrets://tenant/{name}" +); +``` + +Requests whose authorization target cannot be established are denied rather than +authorized on an empty identifier. `completion/complete` requests with a +missing, malformed, or unknown `ref`, or with both a `name` and a `uri`, are +rejected; the legacy bare-string form (`"ref": "prompt-name"`) is also rejected +because it does not say whether it names a prompt or a resource. + +A `subscriptions/listen` request is admitted as a unit: if the policy denies any +URI, the whole request is refused rather than narrowed to the permitted subset, +so the backend never registers a set that differs from the one authorized. At +most 50 URIs may be named in a single request (duplicates count individually +toward the limit but are only evaluated once), and every URI check for the +request shares a single 30-second budget. A `notifications` object carrying a +member outside `toolsListChanged`, `promptsListChanged`, `resourcesListChanged`, +and `resourceSubscriptions` is refused, and an explicit JSON `null` for either +`notifications` or `resourceSubscriptions` is refused for the same reason: an +omitted member and an empty array unambiguously name no resource and pass with +zero checks, but a `null` would leave this proxy and the backend guessing at the +same meaning. + +Policies conditioned on `arg_*` attributes do not match these derived methods. A +completion or subscription request supplies no arguments to the operation being +authorized, so a rule like `when { context.arg_env == "dev" }` denies these +methods rather than matching them. Grant them with a rule that does not test +arguments. + +:::info[Changed in v0.51.0] + +Before v0.51.0, `completion/complete` and `subscriptions/listen` were +always-allowed and bypassed the authorizer entirely. Existing `get_prompt` and +`read_resource` policies now govern completion and subscription access as well. +If your deployment relied on the previous behavior, review policies for prompts +and resources whose completions or subscriptions should stay reachable, and add +a rule for each resource-template URI that clients complete against. + +::: + ### List operations List methods (`tools/list`, `prompts/list`, `resources/list`, @@ -60,15 +126,14 @@ individual-access actions above. See These MCP methods bypass authorization entirely. You cannot write policies to restrict them: -| MCP method | Purpose | -| --------------------- | ---------------------------------- | -| `initialize` | Protocol initialization handshake | -| `ping` | Health check | -| `features/list` | Capability discovery | -| `roots/list` | Root directory discovery | -| `logging/setLevel` | Client logging preference | -| `completion/complete` | Argument auto-completion | -| `notifications/*` | All server-to-client notifications | +| MCP method | Purpose | +| ------------------ | ---------------------------------- | +| `initialize` | Protocol initialization handshake | +| `ping` | Health check | +| `features/list` | Capability discovery | +| `roots/list` | Root directory discovery | +| `logging/setLevel` | Client logging preference | +| `notifications/*` | All server-to-client notifications | ### Denied-by-default MCP methods From 16a9d45087e3763a5bff51c0800b45564c4b6492 Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Wed, 23 Sep 2026 00:20:40 +0000 Subject: [PATCH 4/4] Tighten derived authz prose for clarity Replace engineering shorthand ("admitted as a unit", "leave this proxy and the backend guessing") with reader-facing terms, split the long paragraph on subscriptions/listen into two, and reword the opening so the derivation table reads more naturally. Co-Authored-By: Claude Opus 4.7 --- .../reference/authz-policy-reference.mdx | 32 ++++++++++--------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/docs/toolhive/reference/authz-policy-reference.mdx b/docs/toolhive/reference/authz-policy-reference.mdx index a8423c2c..7815e82c 100644 --- a/docs/toolhive/reference/authz-policy-reference.mdx +++ b/docs/toolhive/reference/authz-policy-reference.mdx @@ -46,9 +46,10 @@ These actions are evaluated against your Cedar policies: | `Action::"read_resource"` | `resources/read` | Read a specific data resource | | `Action::"get_skill"` | `skills/get` | Retrieve a specific skill | -Several other MCP methods name a capability the same actions already govern. -They are authorized as that capability rather than through actions of their own, -so one policy covers the direct method and every method that references it: +Several other MCP methods act on a capability that the actions above already +govern. They are authorized against that capability rather than through actions +of their own, so one policy covers the direct method and every method that +references it: | MCP method | Authorized as | | ---------------------------------------------- | ------------------------------------------------------------------------- | @@ -82,18 +83,19 @@ missing, malformed, or unknown `ref`, or with both a `name` and a `uri`, are rejected; the legacy bare-string form (`"ref": "prompt-name"`) is also rejected because it does not say whether it names a prompt or a resource. -A `subscriptions/listen` request is admitted as a unit: if the policy denies any -URI, the whole request is refused rather than narrowed to the permitted subset, -so the backend never registers a set that differs from the one authorized. At -most 50 URIs may be named in a single request (duplicates count individually -toward the limit but are only evaluated once), and every URI check for the -request shares a single 30-second budget. A `notifications` object carrying a -member outside `toolsListChanged`, `promptsListChanged`, `resourcesListChanged`, -and `resourceSubscriptions` is refused, and an explicit JSON `null` for either -`notifications` or `resourceSubscriptions` is refused for the same reason: an -omitted member and an empty array unambiguously name no resource and pass with -zero checks, but a `null` would leave this proxy and the backend guessing at the -same meaning. +A `subscriptions/listen` request is authorized as a whole: if the policy denies +any URI, the entire request is refused rather than narrowed to the permitted +subset, so the backend never registers a set that differs from the one +authorized. At most 50 URIs may be named in a single request (duplicates count +individually toward the limit but are only evaluated once), and every URI check +shares a single 30-second budget. + +A `notifications` object carrying a member outside `toolsListChanged`, +`promptsListChanged`, `resourcesListChanged`, and `resourceSubscriptions` is +refused. An explicit JSON `null` for either `notifications` or +`resourceSubscriptions` is also refused: an omitted member or empty array +unambiguously names no resource and passes with zero checks, but `null` would +force ToolHive and the backend to guess at its meaning. Policies conditioned on `arg_*` attributes do not match these derived methods. A completion or subscription request supplies no arguments to the operation being