OpenWebUI Tool Server

Wikantik ships an OpenAPI 3.1 tool server under `/tools/` that exposes hybrid search and

page retrieval as LLM-callable tools. It is designed for OpenWebUI and any other OpenAPI

tool runtime; the OpenAPI document at `/tools/openapi.json` advertises two operations:

| Operation | Method + path | Purpose |

|--------------|-----------------------|-------------------------------------------------------|

| `search_wiki` | `POST /tools/search_wiki` | BM25 + hybrid search across the wiki corpus |

| `get_page` | `GET /tools/page/{name}` | Fetch a single page's Markdown body with truncation |

Configuration

Tool-server access has two layers:

1. **DB-backed API keys** (recommended) — generated via the admin UI at

`/admin/apikeys`, bound to a Wikantik principal, and stored SHA-256 hashed. Each

call runs under the key's principal, so page ACLs and JAAS permissions apply

exactly as they would for that user's interactive session.

2. **Transport-layer guards** — CIDR allowlist, rate limits, and the "no config =

fail closed" safety net live in `wikantik-tools.properties`. Defaults ship inside

the module JAR; overrides go in `tomcat/lib/wikantik-tools.properties`.

| Property | Purpose | Default |

|----------------------------------------------|-------------------------------------------------------|---------|

| `tools.access.keys` | Legacy comma-separated Bearer tokens (no principal binding — avoid in new deployments) | *(none)* |

| `tools.access.allowedCidrs` | Comma-separated CIDR allowlist (e.g. `10.0.0.0/8`) | *(none)* |

| `tools.access.allowUnrestricted` | Explicit opt-in to run with no auth and no CIDR | `false` |

| `tools.ratelimit.global` | Global requests per second (0 disables) | `100` |

| `tools.ratelimit.perClient` | Per-client requests per second (0 disables) | `10` |

| `wikantik.public.baseURL` | Public base URL used for citation links + OpenAPI `servers` entry | *(request host)* |

Generating a key

Navigate to **Administration → API Keys** and click **Generate Key**. Pick:

- **Principal** — the login that the tool server should impersonate when running

calls. Page ACLs and JAAS permissions are evaluated against this user.

- **Label** — freeform note (e.g. `OpenWebUI production`) to identify the key later.

- **Scope** — `tools` (OpenAPI only), `mcp` (MCP server only), or `all` (both).

The plaintext token (`wkk_…`) is shown once at creation time and only the SHA-256

hash is persisted. Revoking a key is a single click; any client using the revoked

token starts receiving HTTP 403 immediately.

Fail-closed semantics

When none of the DB-backed keys, legacy `tools.access.keys`, `tools.access.allowedCidrs`,

or `tools.access.allowUnrestricted=true` is configured, the server refuses every request

with `503 Service Unavailable` and logs a CRITICAL line at startup. With at least one

DB-backed key generated via the admin UI, the server accepts Bearer tokens from that

table — no configuration reload required.

Calling the tools

`search_wiki`

```bash

curl -X POST http://localhost:8080/tools/search_wiki \

-H "Authorization: Bearer $TOOLS_KEY" \

-H "Content-Type: application/json" \

-d '{"query": "hybrid retrieval", "maxResults": 5}'

```

Response:

```json

{

"query": "hybrid retrieval",

"results": [

{

"name": "HybridRetrieval",

"url": "https://wiki.example.com/wiki/HybridRetrieval",

"score": 18,

"summary": "Fusion of BM25 and dense vector ranking",

"tags": ["search", "design"],

"snippet": "…fused ranking …",

"lastModified": "2026-04-12T18:22:11Z"

}

],

"total": 1

}

```

`maxResults` is clamped to `[1, 25]`; values outside the range snap to the default of 10.

`get_page`

```bash

curl -H "Authorization: Bearer $TOOLS_KEY" \

"http://localhost:8080/tools/page/HybridRetrieval?maxChars=4000"

```

The response strips YAML frontmatter from `text`, includes `summary`/`tags` from

frontmatter as top-level fields, and reports truncation via `truncated`/`totalChars`/

`truncatedAt` when the body exceeds the limit (default 6000 chars, hard cap 20000).

Observability

The tool server publishes to the process-wide Micrometer registry (scraped at

`/observability/metrics`). All meters are prefixed `wikantik.tools`:

| Meter | Type | Notes |

|-----------------------------------------------|----------|-------|

| `wikantik.tools.requests{endpoint,status}` | counter | endpoint ∈ {search_wiki, get_page, openapi}; status ∈ {success, error, not_found} |

| `wikantik.tools.search.results_returned` | counter | total result rows emitted |

| `wikantik.tools.get_page.truncated` | counter | get_page responses that hit the truncation cap |

OpenWebUI wiring

In OpenWebUI:

1. Workspace → Tools → Add Tool → *OpenAPI URL*.

2. Paste `https://wiki.example.com/tools/openapi.json` and your Bearer token.

3. OpenWebUI reads the two operations verbatim; the per-operation `description` fields

double as the LLM's tool prompts, so no extra authoring is required.

Security notes

- DB-backed keys are bound to a Wikantik principal; each call runs as that user so

page ACLs and JAAS permissions apply exactly as they would for an interactive

session. Pick the principal deliberately — the tool caller inherits their view

permissions, so grant narrowly.

- Legacy config-file keys (`tools.access.keys`) are unbound and behave like a shared

service account that bypasses JAAS. Prefer DB-backed keys for any new deployment

and drop the legacy list once all clients have migrated.

- Rate limits are sliding-window, 1-second buckets; the `Retry-After: 1` header is

returned with every 429.