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
41 changes: 41 additions & 0 deletions api/v1alpha1/claw_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ const (
ConditionTypeProxyConfigured = "ProxyConfigured"
ConditionTypeDevicePairingConfigured = "DevicePairingConfigured"
ConditionTypeMcpServersConfigured = "McpServersConfigured"
ConditionTypeWebSearchConfigured = "WebSearchConfigured"
)

// Annotation keys used on pod templates to trigger rollouts on config changes.
Expand Down Expand Up @@ -263,6 +264,36 @@ type McpEnvFromSecret struct {
SecretRef SecretRefEntry `json:"secretRef"`
}

// WebSearchSpec configures the operator-managed web search provider.
// +kubebuilder:validation:XValidation:rule="self.provider in ['duckduckgo','gemini'] || has(self.secretRef)",message="secretRef is required for API-keyed search providers"
type WebSearchSpec struct {
// Provider selects the web search provider.
// Known values: brave, tavily, duckduckgo, gemini.
// +kubebuilder:validation:MinLength=1
Provider string `json:"provider"`

// SecretRef references a Secret key holding the search API key.
// Required for API-keyed providers (brave, tavily).
// Not needed for key-free (duckduckgo) or LLM-as-search (gemini).
// +optional
SecretRef *SecretRefEntry `json:"secretRef,omitempty"`

// Config is provider-specific configuration merged into
// plugins.entries.<provider>.config.webSearch in operator.json.
// Use for provider-specific tuning (mode, maxResults, etc.).
// +kubebuilder:pruning:PreserveUnknownFields
// +optional
Config *runtime.RawExtension `json:"config,omitempty"`
}

// WebFetchSpec configures the web_fetch tool.
type WebFetchSpec struct {
// Enabled activates the web_fetch tool. Fetched URLs are gated by
// the proxy allowlist.
// +kubebuilder:default=true
Enabled bool `json:"enabled"`
}

// ClawSpec defines the desired state of Claw
type ClawSpec struct {
// ConfigMode controls how operator config is applied on pod start.
Expand All @@ -281,6 +312,16 @@ type ClawSpec struct {
// Map keys are server names as they appear in the mcp.servers config.
// +optional
McpServers map[string]McpServerSpec `json:"mcpServers,omitempty"`

// WebSearch configures the web search provider for the OpenClaw agent.
// +optional
WebSearch *WebSearchSpec `json:"webSearch,omitempty"`

// WebFetch enables the web_fetch tool for arbitrary URL fetching.
// Fetched URLs are gated by the proxy allowlist — only domains
// permitted by credentials, search providers, or builtins are reachable.
// +optional
WebFetch *WebFetchSpec `json:"webFetch,omitempty"`
}

// ClawStatus defines the observed state of Claw
Expand Down
50 changes: 50 additions & 0 deletions api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

62 changes: 62 additions & 0 deletions config/crd/bases/claw.sandbox.redhat.com_claws.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,68 @@ spec:
McpServers declares MCP servers injected into OpenClaw's config.
Map keys are server names as they appear in the mcp.servers config.
type: object
webFetch:
description: |-
WebFetch enables the web_fetch tool for arbitrary URL fetching.
Fetched URLs are gated by the proxy allowlist — only domains
permitted by credentials, search providers, or builtins are reachable.
properties:
enabled:
default: true
description: |-
Enabled activates the web_fetch tool. Fetched URLs are gated by
the proxy allowlist.
type: boolean
required:
- enabled
type: object
webSearch:
description: WebSearch configures the web search provider for the
OpenClaw agent.
properties:
config:
description: |-
Config is provider-specific configuration merged into
plugins.entries.<provider>.config.webSearch in operator.json.
Use for provider-specific tuning (mode, maxResults, etc.).
type: object
x-kubernetes-preserve-unknown-fields: true
provider:
description: |-
Provider selects the web search provider.
Known values: brave, tavily, duckduckgo, gemini.
minLength: 1
type: string
secretRef:
description: |-
SecretRef references a Secret key holding the search API key.
Required for API-keyed providers (brave, tavily).
Not needed for key-free (duckduckgo) or LLM-as-search (gemini).
properties:
key:
description: Key is the key in the Secret's data map
minLength: 1
type: string
name:
description: Name is the name of the Secret
minLength: 1
type: string
role:
description: |-
Role distinguishes multiple secrets for the same credential.
Required when multiple secretRef entries are present (e.g., Slack botToken/appToken).
maxLength: 63
type: string
required:
- key
- name
type: object
required:
- provider
type: object
x-kubernetes-validations:
- message: secretRef is required for API-keyed search providers
rule: self.provider in ['duckduckgo','gemini'] || has(self.secretRef)
type: object
status:
description: ClawStatus defines the observed state of Claw
Expand Down
165 changes: 164 additions & 1 deletion docs/provider-setup.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Provider Setup

This guide covers configuring LLM providers, external services, messaging channels, and MCP servers for use with Claw. Each section walks through creating the necessary Secret and Claw CR configuration.
This guide covers configuring LLM providers, external services, messaging channels, MCP servers, web search, and web fetch for use with Claw. Each section walks through creating the necessary Secret and Claw CR configuration.

All examples assume you have set your target namespace:

Expand Down Expand Up @@ -856,3 +856,166 @@ The operator reconciles `spec.mcpServers` into the `mcp.servers` section of `ope
- **Overwrite mode**: The full config is replaced on every pod start, including MCP servers.

The operator also validates that all `envFrom`-referenced Secrets exist and contain the specified keys. If validation fails, the `McpServersConfigured` condition is set to `False` with a descriptive error message, and `Ready` is set to `False`.

## Web Search

The operator can configure a web search provider for the OpenClaw agent via `spec.webSearch`. Search API keys are injected by the MITM proxy — they never reach the gateway container.

### Brave Search

Uses the [Brave Search API](https://brave.com/search/api/) with an API key injected via the `X-Subscription-Token` header.

**1. Get an API key** from [Brave Search API](https://brave.com/search/api/).

**2. Create the Secret:**

```sh
oc create secret generic brave-search-key \
--from-literal=api-key=YOUR_BRAVE_API_KEY \
-n $NS
```

**3. Add to your Claw CR:**

```sh
oc apply -f - <<EOF
apiVersion: claw.sandbox.redhat.com/v1alpha1
kind: Claw
metadata:
name: instance
namespace: $NS
spec:
credentials: [] # your existing credentials
webSearch:
provider: brave
secretRef:
name: brave-search-key
key: api-key
EOF
```

### Tavily

Uses the [Tavily API](https://tavily.com/) with a bearer token.

**1. Get an API key** from [Tavily](https://app.tavily.com/).

**2. Create the Secret:**

```sh
oc create secret generic tavily-key \
--from-literal=api-key=YOUR_TAVILY_API_KEY \
-n $NS
```

**3. Add to your Claw CR:**

```sh
oc apply -f - <<EOF
apiVersion: claw.sandbox.redhat.com/v1alpha1
kind: Claw
metadata:
name: instance
namespace: $NS
spec:
credentials: [] # your existing credentials
webSearch:
provider: tavily
secretRef:
name: tavily-key
key: api-key
EOF
```

You can pass provider-specific configuration via `spec.webSearch.config`:

```yaml
webSearch:
provider: tavily
secretRef:
name: tavily-key
key: api-key
config:
maxResults: 10
```

### DuckDuckGo

Key-free search — no API key or Secret needed.

```sh
oc apply -f - <<EOF
apiVersion: claw.sandbox.redhat.com/v1alpha1
kind: Claw
metadata:
name: instance
namespace: $NS
spec:
credentials: [] # your existing credentials
webSearch:
provider: duckduckgo
EOF
```

### Gemini (Search Grounding)

Uses Google's Gemini search grounding, which sends a regular Gemini API call with `tools: [{ google_search }]`. This reuses your existing `google` provider credential — no additional Secret is needed.

**Prerequisite:** You must have a `google` provider credential in `spec.credentials`.

```sh
oc apply -f - <<EOF
apiVersion: claw.sandbox.redhat.com/v1alpha1
kind: Claw
metadata:
name: instance
namespace: $NS
spec:
credentials:
- name: google
provider: google
type: apiKey
secretRef:
- name: gemini-api-key
key: api-key
webSearch:
provider: gemini
EOF
```

### How It Works

The operator sets `tools.web.search.provider` in `operator.json` and, for API-keyed providers, adds a proxy route for the search domain with credential injection. A placeholder API key is set in the config so OpenClaw makes the HTTP call; the proxy strips it and injects the real key.

The `WebSearchConfigured` condition tracks the status:
- `True` — provider validated and config injected
- `False` — validation failed (missing secret, missing google credential for gemini, unknown provider)

## Web Fetch

The `web_fetch` tool allows the agent to fetch arbitrary URLs. Enable it via `spec.webFetch`:

```yaml
spec:
webFetch:
enabled: true
```

Fetched URLs are gated by the proxy allowlist. Only domains already permitted by credentials, search providers, or builtin passthroughs are reachable. To allow additional domains for fetching, add `type: none` credential entries:

```sh
oc apply -f - <<EOF
apiVersion: claw.sandbox.redhat.com/v1alpha1
kind: Claw
metadata:
name: instance
namespace: $NS
spec:
credentials:
- name: docs-site
type: none
domain: docs.python.org
webFetch:
enabled: true
EOF
```
Loading
Loading