From 33643cadfe34a218da0ef892cd91a9bf3ec7f6e6 Mon Sep 17 00:00:00 2001 From: Reynier Ortiz Vega Date: Mon, 13 Apr 2026 14:47:27 -0400 Subject: [PATCH 1/5] Update docs for ToolHive v0.19.0 Co-Authored-By: Claude Sonnet 4.6 --- .../toolhive/guides-cli/skills-management.mdx | 31 ++- docs/toolhive/guides-cli/webhooks.mdx | 206 ++++++++++++++++++ docs/toolhive/guides-k8s/remote-mcp-proxy.mdx | 70 +++--- .../guides-k8s/telemetry-and-metrics.mdx | 28 ++- docs/toolhive/guides-vmcp/authentication.mdx | 8 +- .../reference/client-compatibility.mdx | 2 +- sidebars.ts | 1 + 7 files changed, 303 insertions(+), 43 deletions(-) create mode 100644 docs/toolhive/guides-cli/webhooks.mdx diff --git a/docs/toolhive/guides-cli/skills-management.mdx b/docs/toolhive/guides-cli/skills-management.mdx index 47438467..f8c27590 100644 --- a/docs/toolhive/guides-cli/skills-management.mdx +++ b/docs/toolhive/guides-cli/skills-management.mdx @@ -113,12 +113,31 @@ thv skill install my-skill --force If you have multiple supported clients, ToolHive installs the skill for the first one it detects. To control which client receives the skill, use the -`--client` flag: +`--clients` flag with a comma-separated list: ```bash -thv skill install my-skill --client claude-code +thv skill install my-skill --clients claude-code ``` +### Install to multiple clients at once + +You can install a skill to multiple clients in a single command by +comma-separating client names: + +```bash +thv skill install my-skill --clients claude-code,cursor +``` + +To install to every skill-supporting client at once, use the special `all` +value: + +```bash +thv skill install my-skill --clients all +``` + +If any client installation fails, ToolHive rolls back all changes for that +install operation. + See the [client compatibility reference](../reference/client-compatibility.mdx) for the full list of clients that support skills. @@ -391,9 +410,11 @@ If your AI client doesn't see an installed skill: thv skill info ``` -3. Verify the skill files exist in the expected directory. For Claude Code, - user-scoped skills are at `~/.claude/skills//` and project-scoped - skills are at `/.claude/skills//`. +3. Verify the skill files exist in the expected directory. For example: + - **Claude Code**: `~/.claude/skills//` (user) or + `/.claude/skills//` (project) + - **Cursor**: `~/.cursor/skills//` (user) or + `/.cursor/skills//` (project) 4. Restart your AI client to trigger skill discovery. diff --git a/docs/toolhive/guides-cli/webhooks.mdx b/docs/toolhive/guides-cli/webhooks.mdx new file mode 100644 index 00000000..af22e864 --- /dev/null +++ b/docs/toolhive/guides-cli/webhooks.mdx @@ -0,0 +1,206 @@ +--- +title: Delegate tool authorization with webhooks +sidebar_label: Webhook authorization +description: + Configure webhook middleware to delegate MCP tool call authorization to an + external HTTP service when running MCP servers with the ToolHive CLI. +--- + +Webhook middleware lets you delegate MCP tool call authorization to an external +HTTP service. When a client calls an MCP tool, ToolHive sends a request to your +webhook endpoint, which decides whether to allow or deny the call — and +optionally modify it. + +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). + +## Prerequisites + +- The ToolHive CLI installed. See [Install ToolHive](./install.mdx). +- An HTTP endpoint that accepts webhook requests and returns allow/deny + responses in the ToolHive webhook format. + +## How it works + +When webhook middleware is active, every incoming MCP tool call passes through +two middleware types in order: + +1. **Mutating webhooks** — can transform the request before it reaches the MCP + server (for example, to add context or rewrite arguments) +2. **Validating webhooks** — accept or deny the (possibly mutated) request + +If a validating webhook denies the request, ToolHive returns an error to the +client without calling the MCP server. + +## Create a webhook configuration file + +Webhook configuration is defined in a YAML or JSON file. The file has two +top-level keys: `validating` and `mutating`. Each key maps to a list of webhook +definitions. + +```yaml title="webhooks.yaml" +validating: + - name: policy-check + url: https://policy.example.com/validate + failure_policy: fail + timeout: 5s + tls_config: + ca_bundle_path: /etc/toolhive/pki/webhook-ca.crt + +mutating: + - name: request-enricher + url: https://enrichment.example.com/mutate + failure_policy: ignore + # Omitting timeout uses the default of 10s. + tls_config: + insecure_skip_verify: true +``` + +### Webhook fields + +| Field | Required | Description | +| ----------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------- | +| `name` | Yes | Unique identifier for this webhook. Used for deduplication when merging multiple config files. | +| `url` | Yes | HTTPS endpoint to call. HTTP is only allowed when `tls_config.insecure_skip_verify: true`. | +| `failure_policy` | Yes | `fail` (deny the request on webhook error) or `ignore` (allow through on error). | +| `timeout` | No | Maximum wait time for a response. Accepts duration strings like `5s` or `30s`. Minimum: `1s`, maximum: `30s`. Default: `10s`. | +| `tls_config` | No | TLS options for the webhook HTTP client (see below). | +| `hmac_secret_ref` | No | Environment variable name containing an HMAC secret for payload signing. | + +### TLS configuration + +| Field | Description | +| ---------------------- | ------------------------------------------------------------------------------------------------------------ | +| `ca_bundle_path` | Path to a CA certificate bundle for server certificate verification. | +| `client_cert_path` | Path to a client certificate for mutual TLS (mTLS). | +| `client_key_path` | Path to the client private key for mTLS. Both `client_cert_path` and `client_key_path` must be set together. | +| `insecure_skip_verify` | Disable server certificate verification. Use only for development or in-cluster HTTP endpoints. | + +### JSON format + +The same configuration works in JSON format. Timeout values can be either a +duration string (`"5s"`) or a numeric value in nanoseconds: + +```json title="webhooks.json" +{ + "validating": [ + { + "name": "policy-check", + "url": "https://policy.example.com/validate", + "failure_policy": "fail", + "timeout": "5s", + "tls_config": { + "ca_bundle_path": "/etc/toolhive/pki/webhook-ca.crt" + } + } + ], + "mutating": [ + { + "name": "request-enricher", + "url": "https://enrichment.example.com/mutate", + "failure_policy": "ignore", + "tls_config": { + "insecure_skip_verify": true + } + } + ] +} +``` + +## Run an MCP server with webhook middleware + +Pass your webhook configuration file to `thv run` using the `--webhook-config` +flag: + +```bash +thv run fetch --webhook-config /path/to/webhooks.yaml +``` + +You can specify `--webhook-config` multiple times to merge configurations from +several files. If two files define a webhook with the same `name`, the last file +takes precedence: + +```bash +thv run fetch \ + --webhook-config /etc/toolhive/base-webhooks.yaml \ + --webhook-config /etc/toolhive/team-webhooks.yaml +``` + +ToolHive validates all webhook configurations at startup and exits with an error +if any are invalid, so configuration problems surface before the server starts. + +## Failure policies + +The `failure_policy` field controls what happens when ToolHive cannot reach the +webhook endpoint: + +- `fail` — denies the MCP tool call. Use this when your webhook is authoritative + and a connectivity failure should be treated as a security event. +- `ignore` — allows the tool call through. Use this for non-critical webhooks + like logging or enrichment, where availability is not a hard requirement. + +:::warning + +A `422 Unprocessable Entity` response from a webhook is always treated as a +deny, regardless of the `failure_policy`. This prevents malformed payloads from +accidentally being allowed through. + +::: + +## Webhook request format + +ToolHive sends a JSON `POST` request to your webhook URL with this structure: + +```json +{ + "version": "v0.1.0", + "uid": "550e8400-e29b-41d4-a716-446655440000", + "timestamp": "2025-04-13T10:15:30.123Z", + "principal": { + "sub": "user@example.com", + "email": "user@example.com" + }, + "mcp_request": { ... }, + "context": { + "server_name": "fetch", + "source_ip": "127.0.0.1", + "transport": "streamable-http" + } +} +``` + +## Validating webhook response format + +Your validating webhook must respond with HTTP 200 and a JSON body: + +```json +{ + "version": "v0.1.0", + "uid": "550e8400-e29b-41d4-a716-446655440000", + "allowed": true +} +``` + +To deny a request, set `"allowed": false` and optionally include a `message` and +`reason`: + +```json +{ + "version": "v0.1.0", + "uid": "550e8400-e29b-41d4-a716-446655440000", + "allowed": false, + "message": "Tool call denied by policy", + "reason": "insufficient_permissions" +} +``` + +## Next steps + +- [Custom permissions](./custom-permissions.mdx) — configure Cedar-based + authorization policies for MCP servers +- [Authentication](./auth.mdx) — set up OIDC authentication for MCP servers + +## Related information + +- [`thv run` command reference](../reference/cli/thv_run.md) diff --git a/docs/toolhive/guides-k8s/remote-mcp-proxy.mdx b/docs/toolhive/guides-k8s/remote-mcp-proxy.mdx index 36ab3141..e7c5fc91 100644 --- a/docs/toolhive/guides-k8s/remote-mcp-proxy.mdx +++ b/docs/toolhive/guides-k8s/remote-mcp-proxy.mdx @@ -112,7 +112,7 @@ metadata: namespace: toolhive-system spec: # Remote MCP server URL - remoteURL: https://mcp.analytics.example.com + remoteUrl: https://mcp.analytics.example.com # Port to expose the proxy on proxyPort: 8080 @@ -203,7 +203,7 @@ providers: ```yaml {4-6} spec: - remoteURL: https://mcp.example.com + remoteUrl: https://mcp.example.com oidcConfig: type: inline @@ -243,7 +243,7 @@ metadata: name: analytics-proxy namespace: toolhive-system spec: - remoteURL: https://mcp.analytics.example.com + remoteUrl: https://mcp.analytics.example.com oidcConfig: type: configMap configMap: @@ -274,7 +274,7 @@ This example shows different policy patterns: ```yaml {6-9} spec: - remoteURL: https://mcp.example.com + remoteUrl: https://mcp.example.com oidcConfig: # ... OIDC config ... @@ -369,7 +369,7 @@ metadata: name: analytics-proxy namespace: toolhive-system spec: - remoteURL: https://mcp.analytics.example.com + remoteUrl: https://mcp.analytics.example.com # ... other config ... toolConfigRef: name: analytics-tools-filter @@ -414,7 +414,7 @@ metadata: name: analytics-proxy namespace: toolhive-system spec: - remoteURL: https://mcp.analytics.example.com + remoteUrl: https://mcp.analytics.example.com oidcConfig: # ... Company IDP config ... @@ -447,7 +447,7 @@ For non-sensitive values like tenant IDs or correlation headers, use ```yaml {6-9} spec: - remoteURL: https://mcp.analytics.example.com + remoteUrl: https://mcp.analytics.example.com # ... other config ... headerForward: @@ -479,7 +479,7 @@ metadata: name: analytics-proxy namespace: toolhive-system spec: - remoteURL: https://mcp.analytics.example.com + remoteUrl: https://mcp.analytics.example.com # ... other config ... headerForward: @@ -494,7 +494,7 @@ You can combine plaintext and secret-backed headers: ```yaml {6-14} spec: - remoteURL: https://mcp.analytics.example.com + remoteUrl: https://mcp.analytics.example.com # ... other config ... headerForward: @@ -533,7 +533,7 @@ metadata: name: mcp-spec-proxy namespace: toolhive-system spec: - remoteURL: https://modelcontextprotocol.io/mcp + remoteUrl: https://modelcontextprotocol.io/mcp proxyPort: 8080 transport: streamable-http @@ -688,40 +688,49 @@ Audit logs are structured JSON written to stdout: } ``` -### Prometheus metrics +### Shared telemetry configuration -Enable Prometheus metrics to monitor proxy health and usage: +The preferred way to enable telemetry is to reference a shared +`MCPTelemetryConfig` resource using `telemetryConfigRef`. This lets you define +telemetry settings once and reuse them across multiple proxies and MCP servers: -```yaml {6-8} +```yaml {6-8} title="analytics-proxy.yaml" spec: - remoteURL: https://mcp.example.com + remoteUrl: https://mcp.example.com # ... other config ... - telemetry: - prometheus: - enabled: true + telemetryConfigRef: + name: shared-otel + serviceName: analytics-mcp-proxy ``` -Metrics are exposed at `/metrics` and include: +The `MCPTelemetryConfig` resource is created separately. See +[Telemetry and metrics](./telemetry-and-metrics.mdx) for the full configuration +reference and examples. -- `toolhive_mcp_requests_total` - Total MCP requests -- `toolhive_mcp_request_duration_seconds` - Request duration -- `toolhive_mcp_tool_calls_total` - Tool call operations -- `toolhive_mcp_active_connections` - Number of active connections +### Inline telemetry configuration (deprecated) + +:::warning[Deprecated] + +The inline `spec.telemetry` field is deprecated and will be removed in a future +release. Use `telemetryConfigRef` to reference a shared `MCPTelemetryConfig` +resource instead. You cannot set both fields on the same resource. -### OpenTelemetry +::: -For distributed tracing and metrics export: +You can still configure telemetry inline using the `telemetry` field: ```yaml {6-15} spec: - remoteURL: https://mcp.example.com + remoteUrl: https://mcp.example.com # ... other config ... telemetry: + prometheus: + enabled: true openTelemetry: enabled: true - endpoint: https://otel-collector.company.com:4317 + endpoint: otel-collector.monitoring.svc.cluster.local:4318 serviceName: analytics-mcp-proxy tracing: enabled: true @@ -730,6 +739,13 @@ spec: enabled: true ``` +Metrics are exposed at `/metrics` and include: + +- `toolhive_mcp_requests_total` - Total MCP requests +- `toolhive_mcp_request_duration_seconds` - Request duration +- `toolhive_mcp_tool_calls_total` - Tool call operations +- `toolhive_mcp_active_connections` - Number of active connections + See [Telemetry and metrics](./telemetry-and-metrics.mdx) for more information. ## Use with Virtual MCP Server @@ -765,7 +781,7 @@ metadata: namespace: toolhive-system spec: groupRef: my-group # Reference to an MCPGroup - remoteURL: https://mcp.context7.com/mcp + remoteUrl: https://mcp.context7.com/mcp transport: streamable-http proxyPort: 8080 oidcConfig: diff --git a/docs/toolhive/guides-k8s/telemetry-and-metrics.mdx b/docs/toolhive/guides-k8s/telemetry-and-metrics.mdx index 1e6e8421..8f7c9564 100644 --- a/docs/toolhive/guides-k8s/telemetry-and-metrics.mdx +++ b/docs/toolhive/guides-k8s/telemetry-and-metrics.mdx @@ -54,9 +54,10 @@ spec: kubectl apply -f shared-otel-config.yaml ``` -**Step 2: Reference from an MCPServer** +**Step 2: Reference from an MCPServer or MCPRemoteProxy** -Reference the config by name in `telemetryConfigRef`: +Reference the config by name in `telemetryConfigRef`. Both `MCPServer` and +`MCPRemoteProxy` support this field: ```yaml {10-12} title="mcpserver-with-shared-otel.yaml" apiVersion: toolhive.stacklok.dev/v1alpha1 @@ -73,6 +74,21 @@ spec: serviceName: mcp-fetch-server ``` +For an `MCPRemoteProxy`, the reference works the same way: + +```yaml {8-10} title="remote-proxy-with-shared-otel.yaml" +apiVersion: toolhive.stacklok.dev/v1alpha1 +kind: MCPRemoteProxy +metadata: + name: analytics-proxy + namespace: toolhive-system +spec: + remoteUrl: https://mcp.analytics.example.com + telemetryConfigRef: + name: shared-otel + serviceName: analytics-mcp-proxy +``` + :::tip[Service name] Set `serviceName` to a meaningful name for each MCP server. This helps identify @@ -184,10 +200,10 @@ mutually exclusive. :::warning[Deprecated] -The inline `spec.telemetry` field on MCPServer is deprecated and will be removed -in a future release. Use `telemetryConfigRef` to reference a shared -MCPTelemetryConfig resource instead. You cannot set both fields on the same -MCPServer. +The inline `spec.telemetry` field on `MCPServer` and `MCPRemoteProxy` is +deprecated and will be removed in a future release. Use `telemetryConfigRef` to +reference a shared `MCPTelemetryConfig` resource instead. You cannot set both +fields on the same resource. ::: diff --git a/docs/toolhive/guides-vmcp/authentication.mdx b/docs/toolhive/guides-vmcp/authentication.mdx index 1c0853d4..50cb1916 100644 --- a/docs/toolhive/guides-vmcp/authentication.mdx +++ b/docs/toolhive/guides-vmcp/authentication.mdx @@ -189,7 +189,7 @@ spec: source: inline backends: backend-github: - type: external_auth_config_ref + type: externalAuthConfigRef externalAuthConfigRef: name: inject-github ``` @@ -258,11 +258,11 @@ spec: source: inline backends: backend-github: - type: external_auth_config_ref + type: externalAuthConfigRef externalAuthConfigRef: name: inject-github backend-okta-app: - type: external_auth_config_ref + type: externalAuthConfigRef externalAuthConfigRef: name: exchange-okta ``` @@ -577,7 +577,7 @@ spec: source: inline backends: backend-github: - type: external_auth_config_ref + type: externalAuthConfigRef externalAuthConfigRef: name: inject-github ``` diff --git a/docs/toolhive/reference/client-compatibility.mdx b/docs/toolhive/reference/client-compatibility.mdx index 891b30ec..8380eff5 100644 --- a/docs/toolhive/reference/client-compatibility.mdx +++ b/docs/toolhive/reference/client-compatibility.mdx @@ -16,7 +16,7 @@ We've tested ToolHive with these clients: | -------------------------- | :-------: | :----------------: | :------------: | ------------------------------------------- | | GitHub Copilot (VS Code) | ✅ | ✅ | ❌ | v1.102+ or Insiders version ([see note][3]) | | Claude Code | ✅ | ✅ | ✅ | v1.0.27+ | -| Cursor | ✅ | ✅ | ❌ | v0.50.0+ | +| Cursor | ✅ | ✅ | ✅ | v0.50.0+ | | Google Antigravity | ✅ | ✅ | ❌ | | | Gemini CLI | ✅ | ✅ | ❌ | | | Cline (VS Code) | ✅ | ✅ | ❌ | v3.17.10+ | diff --git a/sidebars.ts b/sidebars.ts index a842d280..18ce1725 100644 --- a/sidebars.ts +++ b/sidebars.ts @@ -108,6 +108,7 @@ const sidebars: SidebarsConfig = { items: [ 'toolhive/guides-cli/auth', 'toolhive/guides-cli/token-exchange', + 'toolhive/guides-cli/webhooks', 'toolhive/guides-cli/telemetry-and-metrics', 'toolhive/guides-cli/test-mcp-servers', 'toolhive/guides-cli/build-containers', From f53ecbcbf14c9d96cdaff31b6a8af35eeeb45db6 Mon Sep 17 00:00:00 2001 From: Reynier Ortiz Vega Date: Mon, 13 Apr 2026 18:42:09 -0400 Subject: [PATCH 2/5] Address PR review comments Co-Authored-By: Claude Sonnet 4.6 --- docs/toolhive/guides-cli/webhooks.mdx | 16 ++++++++-------- docs/toolhive/guides-k8s/remote-mcp-proxy.mdx | 1 + 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/docs/toolhive/guides-cli/webhooks.mdx b/docs/toolhive/guides-cli/webhooks.mdx index af22e864..def2eeaf 100644 --- a/docs/toolhive/guides-cli/webhooks.mdx +++ b/docs/toolhive/guides-cli/webhooks.mdx @@ -59,14 +59,14 @@ mutating: ### Webhook fields -| Field | Required | Description | -| ----------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------- | -| `name` | Yes | Unique identifier for this webhook. Used for deduplication when merging multiple config files. | -| `url` | Yes | HTTPS endpoint to call. HTTP is only allowed when `tls_config.insecure_skip_verify: true`. | -| `failure_policy` | Yes | `fail` (deny the request on webhook error) or `ignore` (allow through on error). | -| `timeout` | No | Maximum wait time for a response. Accepts duration strings like `5s` or `30s`. Minimum: `1s`, maximum: `30s`. Default: `10s`. | -| `tls_config` | No | TLS options for the webhook HTTP client (see below). | -| `hmac_secret_ref` | No | Environment variable name containing an HMAC secret for payload signing. | +| Field | Required | Description | +| ----------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------- | +| `name` | Yes | Unique identifier for this webhook. Used for deduplication when merging multiple config files. | +| `url` | Yes | HTTPS endpoint to call. Plain HTTP is also accepted when `tls_config.insecure_skip_verify: true` (for development or in-cluster endpoints). | +| `failure_policy` | Yes | `fail` (deny the request on webhook error) or `ignore` (allow through on error). | +| `timeout` | No | Maximum wait time for a response. Accepts duration strings like `5s` or `30s`. Minimum: `1s`, maximum: `30s`. Default: `10s`. | +| `tls_config` | No | TLS options for the webhook HTTP client (see below). | +| `hmac_secret_ref` | No | Environment variable name containing an HMAC secret for payload signing. | ### TLS configuration diff --git a/docs/toolhive/guides-k8s/remote-mcp-proxy.mdx b/docs/toolhive/guides-k8s/remote-mcp-proxy.mdx index e7c5fc91..37cc5373 100644 --- a/docs/toolhive/guides-k8s/remote-mcp-proxy.mdx +++ b/docs/toolhive/guides-k8s/remote-mcp-proxy.mdx @@ -731,6 +731,7 @@ spec: openTelemetry: enabled: true endpoint: otel-collector.monitoring.svc.cluster.local:4318 + insecure: true serviceName: analytics-mcp-proxy tracing: enabled: true From 28fb84113f4add878a12e66ba0c13769fb6c9498 Mon Sep 17 00:00:00 2001 From: Reynier Ortiz Vega Date: Mon, 13 Apr 2026 19:12:32 -0400 Subject: [PATCH 3/5] Fix webhook url/insecure_skip_verify table descriptions Co-Authored-By: Claude Sonnet 4.6 --- docs/toolhive/guides-cli/webhooks.mdx | 28 +++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/docs/toolhive/guides-cli/webhooks.mdx b/docs/toolhive/guides-cli/webhooks.mdx index def2eeaf..23fb96df 100644 --- a/docs/toolhive/guides-cli/webhooks.mdx +++ b/docs/toolhive/guides-cli/webhooks.mdx @@ -59,23 +59,23 @@ mutating: ### Webhook fields -| Field | Required | Description | -| ----------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------- | -| `name` | Yes | Unique identifier for this webhook. Used for deduplication when merging multiple config files. | -| `url` | Yes | HTTPS endpoint to call. Plain HTTP is also accepted when `tls_config.insecure_skip_verify: true` (for development or in-cluster endpoints). | -| `failure_policy` | Yes | `fail` (deny the request on webhook error) or `ignore` (allow through on error). | -| `timeout` | No | Maximum wait time for a response. Accepts duration strings like `5s` or `30s`. Minimum: `1s`, maximum: `30s`. Default: `10s`. | -| `tls_config` | No | TLS options for the webhook HTTP client (see below). | -| `hmac_secret_ref` | No | Environment variable name containing an HMAC secret for payload signing. | +| Field | Required | Description | +| ----------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------- | +| `name` | Yes | Unique identifier for this webhook. Used for deduplication when merging multiple config files. | +| `url` | Yes | HTTPS endpoint to call. Plain HTTP is accepted for in-cluster or development use (see `insecure_skip_verify` below). | +| `failure_policy` | Yes | `fail` (deny the request on webhook error) or `ignore` (allow through on error). | +| `timeout` | No | Maximum wait time for a response. Accepts duration strings like `5s` or `30s`. Minimum: `1s`, maximum: `30s`. Default: `10s`. | +| `tls_config` | No | TLS options for the webhook HTTP client (see below). | +| `hmac_secret_ref` | No | Environment variable name containing an HMAC secret for payload signing. | ### TLS configuration -| Field | Description | -| ---------------------- | ------------------------------------------------------------------------------------------------------------ | -| `ca_bundle_path` | Path to a CA certificate bundle for server certificate verification. | -| `client_cert_path` | Path to a client certificate for mutual TLS (mTLS). | -| `client_key_path` | Path to the client private key for mTLS. Both `client_cert_path` and `client_key_path` must be set together. | -| `insecure_skip_verify` | Disable server certificate verification. Use only for development or in-cluster HTTP endpoints. | +| Field | Description | +| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `ca_bundle_path` | Path to a CA certificate bundle for server certificate verification. | +| `client_cert_path` | Path to a client certificate for mutual TLS (mTLS). | +| `client_key_path` | Path to the client private key for mTLS. Both `client_cert_path` and `client_key_path` must be set together. | +| `insecure_skip_verify` | Disable TLS certificate verification for HTTPS connections, and also allow plain HTTP endpoint URLs. Use only in development or trusted in-cluster environments. | ### JSON format From 3b90e88de919470d8e991aef2af8ee401340103f Mon Sep 17 00:00:00 2001 From: Reynier Ortiz Vega Date: Tue, 14 Apr 2026 10:29:08 -0400 Subject: [PATCH 4/5] Address editorial review comments Co-Authored-By: Claude Sonnet 4.6 --- .../toolhive/guides-cli/skills-management.mdx | 2 +- docs/toolhive/guides-cli/webhooks.mdx | 50 +++++++++++++++---- docs/toolhive/guides-k8s/remote-mcp-proxy.mdx | 16 +++--- 3 files changed, 51 insertions(+), 17 deletions(-) diff --git a/docs/toolhive/guides-cli/skills-management.mdx b/docs/toolhive/guides-cli/skills-management.mdx index f8c27590..1d5de960 100644 --- a/docs/toolhive/guides-cli/skills-management.mdx +++ b/docs/toolhive/guides-cli/skills-management.mdx @@ -113,7 +113,7 @@ thv skill install my-skill --force If you have multiple supported clients, ToolHive installs the skill for the first one it detects. To control which client receives the skill, use the -`--clients` flag with a comma-separated list: +`--clients` flag: ```bash thv skill install my-skill --clients claude-code diff --git a/docs/toolhive/guides-cli/webhooks.mdx b/docs/toolhive/guides-cli/webhooks.mdx index 23fb96df..bc214b6f 100644 --- a/docs/toolhive/guides-cli/webhooks.mdx +++ b/docs/toolhive/guides-cli/webhooks.mdx @@ -8,7 +8,7 @@ description: Webhook middleware lets you delegate MCP tool call authorization to an external HTTP service. When a client calls an MCP tool, ToolHive sends a request to your -webhook endpoint, which decides whether to allow or deny the call — and +webhook endpoint, which decides whether to allow or deny the call, and optionally modify it. Use webhooks when your authorization logic is too complex for static Cedar @@ -26,9 +26,9 @@ as a policy engine or an OPA server). When webhook middleware is active, every incoming MCP tool call passes through two middleware types in order: -1. **Mutating webhooks** — can transform the request before it reaches the MCP +1. **Mutating webhooks** can transform the request before it reaches the MCP server (for example, to add context or rewrite arguments) -2. **Validating webhooks** — accept or deny the (possibly mutated) request +2. **Validating webhooks** accept or deny the (possibly mutated) request If a validating webhook denies the request, ToolHive returns an error to the client without calling the MCP server. @@ -135,10 +135,10 @@ if any are invalid, so configuration problems surface before the server starts. The `failure_policy` field controls what happens when ToolHive cannot reach the webhook endpoint: -- `fail` — denies the MCP tool call. Use this when your webhook is authoritative +- `fail` denies the MCP tool call. Use this when your webhook is authoritative and a connectivity failure should be treated as a security event. -- `ignore` — allows the tool call through. Use this for non-critical webhooks - like logging or enrichment, where availability is not a hard requirement. +- `ignore` allows the tool call through. Use this for non-critical webhooks like + logging or enrichment, where availability is not a hard requirement. :::warning @@ -195,11 +195,43 @@ To deny a request, set `"allowed": false` and optionally include a `message` and } ``` +## Mutating webhook response format + +Your mutating webhook must respond with HTTP 200. To pass the request through +unchanged, return an allow response with no patch: + +```json +{ + "version": "v0.1.0", + "uid": "550e8400-e29b-41d4-a716-446655440000", + "allowed": true +} +``` + +To modify the request, include a `patch_type` and a `patch` containing +[JSON Patch](https://jsonpatch.com/) operations that will be applied to the +`mcp_request`: + +```json +{ + "version": "v0.1.0", + "uid": "550e8400-e29b-41d4-a716-446655440000", + "allowed": true, + "patch_type": "json_patch", + "patch": [ + { "op": "add", "path": "/params/context", "value": "injected-by-webhook" } + ] +} +``` + +A mutating webhook can also deny a request by setting `"allowed": false`, in +which case `patch_type` and `patch` are ignored. + ## Next steps -- [Custom permissions](./custom-permissions.mdx) — configure Cedar-based - authorization policies for MCP servers -- [Authentication](./auth.mdx) — set up OIDC authentication for MCP servers +- [Custom permissions](./custom-permissions.mdx) for Cedar-based authorization + policies for MCP servers +- [Authentication](./auth.mdx) to set up OIDC authentication for MCP servers ## Related information diff --git a/docs/toolhive/guides-k8s/remote-mcp-proxy.mdx b/docs/toolhive/guides-k8s/remote-mcp-proxy.mdx index b0f011ae..4e6d6733 100644 --- a/docs/toolhive/guides-k8s/remote-mcp-proxy.mdx +++ b/docs/toolhive/guides-k8s/remote-mcp-proxy.mdx @@ -708,6 +708,15 @@ The `MCPTelemetryConfig` resource is created separately. See [Telemetry and metrics](./telemetry-and-metrics.mdx) for the full configuration reference and examples. +### Available Prometheus metrics + +When Prometheus is enabled, metrics are exposed at `/metrics` and include: + +- `toolhive_mcp_requests_total` - Total MCP requests +- `toolhive_mcp_request_duration_seconds` - Request duration +- `toolhive_mcp_tool_calls_total` - Tool call operations +- `toolhive_mcp_active_connections` - Number of active connections + ### Inline telemetry configuration (deprecated) :::warning[Deprecated] @@ -740,13 +749,6 @@ spec: enabled: true ``` -Metrics are exposed at `/metrics` and include: - -- `toolhive_mcp_requests_total` - Total MCP requests -- `toolhive_mcp_request_duration_seconds` - Request duration -- `toolhive_mcp_tool_calls_total` - Tool call operations -- `toolhive_mcp_active_connections` - Number of active connections - See [Telemetry and metrics](./telemetry-and-metrics.mdx) for more information. ## Use with Virtual MCP Server From 7c9dc9876c5389add7cd0f683bf7d263bfbaa549 Mon Sep 17 00:00:00 2001 From: Radoslav Dimitrov Date: Tue, 14 Apr 2026 17:47:31 +0300 Subject: [PATCH 5/5] Fix webhook docs: patch path prefix and hmac_secret_ref status - Fix JSON Patch example path from /params/context to /mcp_request/params/context (required by middleware) - Note that hmac_secret_ref is not yet implemented upstream Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/toolhive/guides-cli/webhooks.mdx | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/docs/toolhive/guides-cli/webhooks.mdx b/docs/toolhive/guides-cli/webhooks.mdx index bc214b6f..64f98ed1 100644 --- a/docs/toolhive/guides-cli/webhooks.mdx +++ b/docs/toolhive/guides-cli/webhooks.mdx @@ -59,14 +59,14 @@ mutating: ### Webhook fields -| Field | Required | Description | -| ----------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------- | -| `name` | Yes | Unique identifier for this webhook. Used for deduplication when merging multiple config files. | -| `url` | Yes | HTTPS endpoint to call. Plain HTTP is accepted for in-cluster or development use (see `insecure_skip_verify` below). | -| `failure_policy` | Yes | `fail` (deny the request on webhook error) or `ignore` (allow through on error). | -| `timeout` | No | Maximum wait time for a response. Accepts duration strings like `5s` or `30s`. Minimum: `1s`, maximum: `30s`. Default: `10s`. | -| `tls_config` | No | TLS options for the webhook HTTP client (see below). | -| `hmac_secret_ref` | No | Environment variable name containing an HMAC secret for payload signing. | +| Field | Required | Description | +| ----------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | +| `name` | Yes | Unique identifier for this webhook. Used for deduplication when merging multiple config files. | +| `url` | Yes | HTTPS endpoint to call. Plain HTTP is accepted for in-cluster or development use (see `insecure_skip_verify` below). | +| `failure_policy` | Yes | `fail` (deny the request on webhook error) or `ignore` (allow through on error). | +| `timeout` | No | Maximum wait time for a response. Accepts duration strings like `5s` or `30s`. Minimum: `1s`, maximum: `30s`. Default: `10s`. | +| `tls_config` | No | TLS options for the webhook HTTP client (see below). | +| `hmac_secret_ref` | No | Environment variable name containing an HMAC secret for payload signing. **Not yet implemented** - accepted in config but currently has no effect. | ### TLS configuration @@ -209,8 +209,9 @@ unchanged, return an allow response with no patch: ``` To modify the request, include a `patch_type` and a `patch` containing -[JSON Patch](https://jsonpatch.com/) operations that will be applied to the -`mcp_request`: +[JSON Patch](https://jsonpatch.com/) operations. All patch paths must be +prefixed with `/mcp_request/` because the middleware wraps the MCP body in an +envelope before applying patches: ```json { @@ -219,7 +220,11 @@ To modify the request, include a `patch_type` and a `patch` containing "allowed": true, "patch_type": "json_patch", "patch": [ - { "op": "add", "path": "/params/context", "value": "injected-by-webhook" } + { + "op": "add", + "path": "/mcp_request/params/context", + "value": "injected-by-webhook" + } ] } ```