---
summary: "`obsidian_search_notes` gains BM25-ranked Omnisearch mode (auto-detected) and MCP-spec cursor pagination across all branches; `obsidian_list_commands` gains a `nameRegex` filter; PATCH headers track markdown-patch 1.0 from Local REST API v4.0.0+."
breaking: true
security: false
---

# 3.2.0 — 2026-05-17

Two new agent-facing affordances on `obsidian_search_notes` ranked search and proper pagination; one quality-of-life filter on `obsidian_list_commands`; and a wire-format catch-up for the Local REST API v4.0.0 markdown-patch 1.0 header rename. Minimum supported plugin floor is now v4.0.0.

## Added

- **`obsidian_search_notes` `omnisearch` mode** — BM25-ranked search via the community [Omnisearch](https://github.com/scambier/obsidian-omnisearch) plugin. Supports quoted phrases, `-exclusion`, `path:` / `ext:` filters, typo tolerance, and PDF + OCR coverage (via [Text Extractor](https://github.com/scambier/obsidian-text-extractor)). Auto-detected: probed once at startup against `/search?q=` (200 + `application/json` + JSON-array body — unrouted paths return 200 with empty body, so status alone is insufficient); if reachable, the mode appears in the input enum, otherwise it's omitted so the LLM never sees it as an option. Upstream hard-caps results at 50 — the omnisearch branch carries `truncated: true` when that cap was likely hit. Resolves [#51](https://github.com/cyanheads/obsidian-mcp-server/issues/51).
- **`OBSIDIAN_OMNISEARCH_URL`** — override the derived Omnisearch URL (default: `OBSIDIAN_BASE_URL` host + port `51361`, with `127.0.0.1` mapped to `localhost`). Restart to re-probe.
- **`obsidian_list_commands` `nameRegex` filter** — optional ECMAScript regex matched against the command display name. Reuses the ReDoS guards from `obsidian_list_tags` (≤256 chars, static rejection of nested-quantifier shapes) via the new shared helper. Response echoes `appliedFilters: { nameRegex }`. Resolves [#53](https://github.com/cyanheads/obsidian-mcp-server/issues/53).
- **`src/mcp-server/tools/definitions/_shared/regex-safety.ts`** — `nameRegexSafetyIssue(pattern)` + `NAME_REGEX_MAX_LENGTH` lifted out of `obsidian-list-tags.tool.ts` so both filter tools share one implementation. Mirrors the existing `_shared/schemas.ts` pattern.
- **`ObsidianService.probeOmnisearch(signal?)`** — one-shot 500ms startup probe. Validates status + content-type + array body.
- **`ObsidianService.searchOmnisearch(ctx, query)`** — normalizes the upstream payload: renames `path` → `filename` (so `PathPolicy.filterReadable` composes), decodes HTML entities + converts `<br>` → `\n` in `excerpt`, drops `vault`. Throws `omnisearch_unreachable` (ServiceUnavailable, retryable) on mid-session failures.
- **`buildSearchNotesTool({ omnisearchReachable })`** factory replaces the static `obsidianSearchNotes` export — entry point passes the live probe result so the schema reflects what's actually callable. The module still exports a static specimen with `omnisearchReachable: false` for the MCP definition linter and existing tests.

## Changed

- **`obsidian_search_notes` switched from cap+overflow to MCP-spec cursor pagination** (per spec [2025-06-18](https://modelcontextprotocol.io/specification/2025-06-18/utils/pagination)) via the framework's `paginateArray` helper. Output now carries `totalCount: number` (post-path-policy, pre-pagination) on every mode and `nextCursor?: string` when a successor page exists. Invalid cursors throw `InvalidParams` per spec. Path-policy denial count stays hidden.
- **PATCH header rename** — `Apply-If-Content-Preexists` → `Reject-If-Content-Preexists` with sense inversion, tracking [coddingtonbear/obsidian-local-rest-api v4.0.0](https://github.com/coddingtonbear/obsidian-local-rest-api/releases/tag/4.0.0) (markdown-patch 1.0). The public `applyIfContentPreexists` flag stays on the schema for caller stability and `ObsidianService.#buildPatchHeaders` inverts on the way out so the public default (`false`) sends `Reject-If-Content-Preexists: true` and preserves the historical idempotent-by-default behavior. Replace operations are plugin-exempt regardless. **Minimum supported Local REST API: v4.0.0.**
- **Startup banner** — adds `omnisearchUrl` and `omnisearchReachable` to the structured request context, plus a follow-up info line that reports the resolved URL and which way the mode toggled. The reachability message tells the operator to set `OBSIDIAN_OMNISEARCH_URL` or enable the plugin's HTTP server when the probe fails.
- **`initObsidianService(config)` moved from `createApp.setup()` to module-load order** in `src/index.ts`. Required because `setup()` runs after tools are passed into `createApp()`, which is too late for the Omnisearch probe to influence `obsidian_search_notes`' mode enum.
- **`tools/definitions/index.ts`** — `obsidianSearchNotes` no longer included in `readToolDefinitions`; entry point injects `buildSearchNotesTool(...)` directly. `baseToolDefinitions` removed (one stale caller).
- **`obsidian_list_tags` description** — pointer to discover notes by tag now references `jsonlogic` (`{"in": ["work", {"var": "tags"}]}`) instead of the removed `dataview` mode.
- **CLAUDE.md** — `tools/definitions/index.ts` description updated for the new factory; `src/` file map mentions `_shared/regex-safety.ts` and the Omnisearch-aware factory.
- **README** — `obsidian_search_notes` row and dedicated section rewritten for the new mode set, cursor pagination, and Omnisearch caveats. `OBSIDIAN_OMNISEARCH_URL` row added to the env-var table. Prerequisite raised to **Local REST API v4.0.0+**.

## Removed

- **`obsidian_search_notes` `dataview` mode** — [Local REST API v4.0.0 removed Dataview DQL search support](https://github.com/coddingtonbear/obsidian-local-rest-api/releases/tag/4.0.0); the mode is gone with it. Use `jsonlogic` for path/frontmatter/stat filtering, or `omnisearch` for ranked relevance.
- **`ObsidianService.searchDataview(ctx, dql)`** — sole caller removed with the mode. The `application/vnd.olrapi.dataview.dql+txt` content type constant also goes.
- **`obsidian_search_notes` `excluded` overflow indicator** — replaced by `totalCount` + `nextCursor`. The `EXCLUSION_HINT` constant is removed.

## Fixed

- **`safeCodePoint` (excerpt entity decoder)** — drops a dead `try/catch` around `String.fromCodePoint`; the preceding range guard already covers every `RangeError` condition per spec.

## Migration

| Before | After |
|:--|:--|
| `obsidian_search_notes` with `mode: 'dataview'` | Removed upstream in Local REST API v4.0.0 (Dataview DQL search). Switch to `mode: 'jsonlogic'` (path / frontmatter / stat filters) or `mode: 'omnisearch'` (ranked relevance, when reachable). |
| Reading `result.excluded.count` | Read `result.totalCount` and check whether `result.nextCursor` is set. |
| Paging through > 100 hits | Pass `cursor: result.nextCursor` on the next call; the page size is server-determined (50 default, 200 max). |
| Sending `Apply-If-Content-Preexists` against Local REST API < 4.0.0 | Upgrade the plugin to v4.0.0 or later. The header was renamed upstream; the wire format is incompatible with older builds. |
