# Tools Reference

open-zk-kb exposes ten MCP tools. Your agent calls these automatically based on injected [instructions](setup-guide.md#agent-instructions) — you rarely need to invoke them manually.

| Tool | What it does |
|------|-------------|
| [`knowledge-store`](#knowledge-store) | Save decisions, preferences, procedures, and insights |
| [`knowledge-ingest`](#knowledge-ingest) | Extract article content from URLs or HTML into structured markdown |
| [`knowledge-search`](#knowledge-search) | Search the knowledge base before starting work |
| [`knowledge-maintain`](#knowledge-maintain) | Review, promote, archive, and rebuild notes |
| [`knowledge-health`](#knowledge-health) | Vault health metrics, staleness distribution, growth rates, and infrastructure status |
| [`knowledge-context`](#knowledge-context) | Get a global or project overview with computed inventory and recent activity |
| `knowledge-template` | Get the canonical note template for a specific kind |
| `knowledge-mine` | Bulk-screen candidates from session history for duplicates and store |
| `knowledge-open` | Open the vault in [Obsidian](obsidian.md) with a scaffolded theme, plugins, and homepage |
| [`knowledge-get`](#knowledge-get) | Retrieve a single note by its exact ID |

Notes use [9 kinds with lifecycle management](note-lifecycle.md). For configuration options, see the [Configuration Reference](configuration.md).

---

## knowledge-store

Store knowledge in the persistent knowledge base. One concept per note.

### Parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `title` | string | Yes | Note title — concise, descriptive |
| `content` | string | Yes | Note content — the knowledge to store |
| `kind` | enum | Yes | One of: `personalization`, `reference`, `decision`, `procedure`, `resource`, `observation`, `domain`. Note: `index` and `log` are auto-generated and cannot be created manually |
| `summary` | string | Yes | One-line present-tense key takeaway |
| `guidance` | string | Yes | Imperative actionable instruction for future agents |
| `status` | enum | No | Override default: `fleeting`, `permanent`, or `archived`. Defaults based on kind (see [Note Lifecycle](note-lifecycle.md)) |
| `lifecycle` | enum | No | `living` (mutable), `snapshot` (immutable), or `append-only`. Defaults based on kind |
| `tags` | string[] | No | Tags for categorization |
| `project` | string | No | Project scope — auto-adds `project:<name>` tag |
| `client` | string | No | Client identifier (e.g. `claude-code`, `opencode`). Auto-detected from content when omitted |
| `related` | string[] | No | IDs of related notes to link via `[[wikilinks]]` |
| `model` | string | No | Your model identifier. Enables richer responses for capable models |

### Domain note constraints

When `kind: "domain"`:
- **Requires** `project` parameter — rejected without one
- **One per project** — storing a second domain note for the same project returns an error with the existing note's ID
- Default status: `permanent`, default lifecycle: `living`

### What happens

1. Generates a timestamped ID (`YYYYMMDDHHmmss00`)
2. Creates a Markdown file with YAML frontmatter at `{vault}/{id}-{slug}.md`
3. Indexes the note in SQLite for full-text and metadata search
4. Generates a local embedding vector for semantic search (if enabled)
5. Checks for near-duplicates via SimHash and warns if found
6. Tracks wikilink relationships in the `note_links` table
7. If `project` is set: auto-rebuilds the project's `index` note (a catalog of all project notes grouped by kind) and appends an entry to the project's `log` note (a chronological operations log)

### Auto-generated notes

When storing a project-scoped note, two structural notes are maintained automatically:

- **index** — a living project shell page maintained by the server. It is primarily a human-facing Obsidian navigation surface and may use Dataview to render live note lists and counts inside the shell. Rebuilt on every store, archive, promote, delete, or rebuild event. One per project.
- **log** — an append-only chronological log of events (stores, promotions, archives, deletions, rebuilds). It is primarily a human-facing Obsidian activity surface. Each entry has a bold date prefix. One per project.

These notes are auto-generated by the server when `navigation.enableProjectIndex` / `navigation.enableProjectLog` are enabled (both default to `true`). Agents cannot create `index` or `log` notes manually via `knowledge-store`.

### Example

```json
{
  "title": "Prefer Bun over Node for this project",
  "content": "The project uses bun:sqlite and Bun-specific APIs. Node.js is blocked via engines field in package.json (node >=99.0.0).",
  "kind": "decision",
  "summary": "Bun is the required runtime — Node.js is intentionally blocked.",
  "guidance": "Never suggest Node.js alternatives. Always use bun commands.",
  "tags": ["runtime", "tooling"],
  "project": "open-zk-kb"
}
```

---

## knowledge-ingest

Extract article content as clean markdown from a URL or pre-fetched HTML. Returns title, content, word count, and metadata. Deterministic — no LLM dependency.

### Parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `url` | string | No* | URL to fetch and extract content from. Must be `http://` or `https://` |
| `html` | string | No* | Raw HTML to extract from. Use when you already fetched the page via another tool |

\* At least one of `url` or `html` must be provided. When both are provided, `html` is used for extraction and `url` is used for resolving relative links.

### What happens

1. **If `url` only** — fetches the page with SSRF protection (literal private IP/hostname pattern blocking, redirect validation, streaming size limits), then extracts
2. **If `html` only** — extracts directly from provided HTML (no network request). Pass `url` alongside `html` when possible — without it, relative links cannot be resolved
3. **If both** — extracts from `html`, uses `url` for relative link resolution
4. Extracts article content using Mozilla Readability (same engine as Firefox Reader View)
5. Converts to clean markdown (headings, lists, links preserved; images, iframes, nav stripped)
6. Returns structured metadata: title, word count, byline, excerpt, site name

### Usage patterns

**Preferred: Pre-fetched HTML** — use your web tools (Playwright, Exa, web_fetch) to fetch first, then pass the HTML for extraction. This handles JavaScript-rendered pages, bot-protected sites, and authenticated content:
```json
{
  "url": "https://example.com/blog/article",
  "html": "<html>...</html>"
}
```

**HTML only** — when you have HTML but no source URL:
```json
{ "html": "<html><body><article>...</article></body></html>" }
```

**Fallback: Direct URL** — when no web tools are available. The built-in fetcher is basic — see limitations below:
```json
{ "url": "https://example.com/blog/article" }
```

### Built-in fetcher limitations

The `url`-only path uses a basic HTTP client. It **cannot** handle:

- JavaScript-rendered pages (SPAs, React, Vue, Next.js client-side)
- Bot-protected sites (CloudFlare, Akamai, CAPTCHAs)
- Authenticated or paywalled content
- Cookie consent modals
- Lazy-loaded or infinite-scroll content

For these cases, fetch the page with a browser-capable tool and pass the `html` parameter.

### Security notes

- **SSRF protection** blocks literal private/reserved IP addresses and hostname patterns (localhost, 10.x, 100.64-127.x, 172.16-31.x, 192.168.x, 169.254.x, IPv6 loopback/link-local/unique-local, IPv4-mapped IPv6). Each initial request and redirect hop is validated before fetching, including DNS resolution of hostnames with every returned address checked against private/reserved ranges.
- **Known limitation**: DNS is resolved before each fetch, but there remains a time-of-check/time-of-use window between validation and the HTTP connection. A hostname could theoretically rebind after validation but before `fetch()` connects.
- **Extracted links** are filtered to exclude private/reserved IP targets before being shown in output.
- **URL credentials** (userinfo) are stripped from extracted links.

### Workflow

The intended workflow is a two-step pipeline:

1. *(If you have web tools)* Fetch with Playwright/Exa/web_fetch → pass to `knowledge-ingest(html: ...)`
2. `knowledge-ingest` → extract and review content
3. `knowledge-store` → save as a knowledge base note

The tool extracts content but does **not** create notes automatically. This keeps summarization and note structuring at the agent layer.

---

## knowledge-search

Search the knowledge base using full-text search and semantic similarity. Returns matching notes ranked by relevance.

### Parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `query` | string | Yes | Natural language query or keywords |
| `kind` | enum | No | Filter by note kind |
| `status` | enum | No | Filter by status: `fleeting`, `permanent`, or `archived` |
| `lifecycle` | enum | No | Filter by lifecycle: `living`, `snapshot`, or `append-only` |
| `project` | string | No | Filter by project tag |
| `client` | string | No | Your client name — excludes notes scoped to other clients |
| `tags` | string[] | No | Filter by tags (all must match) |
| `limit` | number | No | Max results (default 10) |

### How search works

1. **Full-text search** — tokenizes the query, strips special operators, searches title + content + tags + context
2. **Semantic embedding search** — if embeddings are enabled, generates a query vector and finds cosine-similar notes (races against a 500ms timeout)
3. **Reciprocal Rank Fusion** — merges both result sets into a single ranked list
4. **Domain note injection** — when a `project` filter is set and the project has a `domain` note, it is prepended to results regardless of relevance ranking. Configurable via `search.alwaysIncludeDomainNote` (default: `true`)
5. If the embedding model isn't ready (first query after startup), gracefully falls back to full-text-only

### Example

```json
{
  "query": "how do we handle authentication",
  "kind": "decision",
  "project": "my-app",
  "limit": 5
}
```

---

## knowledge-maintain

Maintain the knowledge base: view stats, review aging notes, find duplicates, promote/archive/delete notes, rebuild the index, and manage data migrations.

### Parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `action` | enum | Yes | See actions table below |
| `noteId` | string | No | Note ID (required for `promote`, `archive`, `delete`; migration ID for `upgrade-read`) |
| `filter` | enum | No | For `review`: `fleeting` or `permanent` |
| `days` | number | No | Days threshold for `review` (default: from `lifecycle.reviewAfterDays` config) |
| `limit` | number | No | Max notes to show (default: 3 for `review`) |
| `dryRun` | boolean | No | Preview changes without applying |

### Actions

| Action | Description | Requires `noteId` |
|--------|-------------|-------------------|
| `review` | Surface notes that haven't been accessed recently for triage | No |
| `dedupe` | Find near-duplicate notes using SimHash similarity | No |
| `promote` | Move a fleeting note to permanent status | Yes |
| `archive` | Move a note to archived status | Yes |
| `delete` | Permanently delete a note (file + DB + FTS + links) | Yes |
| `rebuild` | Reconstruct the SQLite database from Markdown files on disk | No |
| `embed` | Backfill missing embedding vectors for existing notes | No |
| `agent-docs` | Audit or repair managed agent instruction files | No |
| `upgrade` | List pending data migrations | No |
| `upgrade-read` | Read a specific migration's instructions | Yes (migration ID) |
| `upgrade-apply` | Apply a data migration | Yes (migration ID) |
| `format` | Re-serialize all note files with canonical frontmatter and navigation | No |
| `scope-audit` | Detect incorrectly scoped client tags | No |
| `preference-audit` | Report deterministic quality signals in active personalization notes; read-only and non-mutating | No |
| `unlinked` | Find notes with no wikilinks | No |
| `broken-links` | Find wikilinks to non-existent notes | No |
| `link-health` | Combined report: unlinked + broken links + one-way links | No |
| `migrate-layout` | Move flat vault to kind-based directory structure | No |
| `upgrade-vault` | Refresh Obsidian scaffold assets | No |
| `full` | Composite: rebuild → migrate-layout → format → dedupe → embed → link-health (one-command maintenance) | No |

### Examples

```json
{ "action": "full", "dryRun": true }
```

```json
{ "action": "review", "filter": "fleeting", "days": 14, "limit": 5 }
```

```json
{ "action": "promote", "noteId": "2026030919130100" }
```

```json
{ "action": "rebuild", "dryRun": true }
```

```json
{ "action": "preference-audit", "dryRun": true }
```

### Preference audit output

`preference-audit` scans non-archived personalization notes and reports deterministic matched evidence, including temporary wording, exact filesystem or client configuration paths, hex colors, model identifiers or routing language, configuration verbs, and missing applicability tags when scoped technology names appear. A clean scan reports that no preference quality signals were found.

The action is always read-only: it does not reclassify, archive, edit, or otherwise mutate notes. Its results are evidence for caller judgment, not instructions to take an action. Applicability remains represented by `project:*` and `client:*` tags; absence of both means universal.

---

## knowledge-health

Standalone tool for vault health metrics, staleness distribution, growth rates, and infrastructure status.

### Parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `project` | string | No | Scope metrics to a specific project |
| `period` | string | No | Time window for growth rates: `"7d"`, `"30d"`, or `"90d"` (default: `"30d"`) |
| `telemetry` | boolean | No | Include 30-day local-only tool invocation aggregates |
| `model` | string | No | Your model identifier. Enables richer responses for capable models |

### What it returns

- **Health counts** — notes by status (fleeting, permanent, archived) and total
- **Embedding coverage** — how many notes have embeddings vs total
- **Link health summary** — broken links, unlinked notes, one-way links
- **Staleness distribution** — notes bucketed by days since last access
- **Growth rate by kind** — new notes per kind within the selected period
- **Infrastructure** — vault layout mode, Obsidian scaffold status, git status
- **Version** — current open-zk-kb version
- **Telemetry** (opt-in) — 30-day tool invocation aggregates when `telemetry: true`

### Example

```json
{ "project": "my-app", "period": "7d", "telemetry": true }
```

---

## knowledge-context

Get a global or project-scoped overview with a computed inventory of notes, recent activity, and resources. Use at the start of a session to orient yourself.

The Pi extension also requests a compact project preference capsule from this tool when a session starts. It injects the capsule through the system prompt and displays a separate, deduplicated TUI entry; the model does not need to initiate a search. See the [Pi Experience](pi.md#automatic-project-preferences).

### Parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `project` | string | No | Project name. Omit for a global overview across all projects |
| `logEntries` | number | No | Number of recent log entries to include (default: 10) |
| `includePreferences` | boolean | No | Include a compact capsule of matching permanent personalization notes |
| `client` | string | No | Client identifier used to include matching client-scoped preferences |
| `model` | string | No | Your model identifier. Enables richer responses for capable models |

### Global overview (no `project`)

Returns a high-level view across the entire vault:

- List of all projects
- Global inventory — note counts by kind
- Recent notes across all projects
- Resources

### Project overview (`project` provided)

Returns a focused view of a single project:

- **Domain note** — the project's domain note content (if one exists)
- **Inventory by kind** — note counts broken down by kind for the project
- **Recent notes** — recently created or accessed notes in the project
- **Resources** — resource notes scoped to the project
- **Activity log** — recent operations log entries (controlled by `logEntries`)

### Example

```json
{ "project": "my-app", "logEntries": 5 }
```
---

## knowledge-get

Retrieve a single note by its exact ID. Faster and more precise than knowledge-search. Use when you already know the note ID (e.g. from search results, context hints, or wikilink references).

### Parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `noteId` | string | Yes | Exact note ID to retrieve |
| `model` | string | No | Your model identifier. Enables richer responses for capable models |

### What happens

1. Looks up the note by ID in the SQLite index
2. Returns the full note content in the same format as search results
3. Returns an error message if no note exists with the given ID

### When to use

- **Following references**: Search results and overview pages contain note IDs. Use `knowledge-get` to retrieve referenced notes without a full search.
- **After store**: The `knowledge-store` response includes the new note's ID. Use `knowledge-get` to verify or retrieve it later.
- **Compact rendering hints**: When `renderNoteForAgent` shows a truncated content preview with a hint, use `knowledge-get` with the note's ID to get the full content.

### Example

```json
{ "noteId": "2026030919130100" }
```
