@oomkapwn/enquire-mcp 2.9.0 → 2.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,62 @@
2
2
 
3
3
  All notable changes to this project will be documented here. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
4
4
 
5
+ ## [2.10.0] — 2026-05-08
6
+
7
+ **Sprint 10 — OCR for image-only / scanned PDFs.** Closes the v2.7-v2.8-v2.9 PDF retrieval story. v2.7.0 added text-extraction tools; v2.8.0 blended PDF chunks into hybrid search; v2.9.0 added cross-encoder reranking. v2.10.0 makes the **scanned / camera-captured** PDFs in your vault searchable too — Tesseract.js OCR over each page bitmap.
8
+
9
+ ### Added — `obsidian_ocr_pdf`
10
+
11
+ Runs Tesseract OCR over each page of an image-only / scanned PDF and returns the same shape as `obsidian_read_pdf` plus a per-page `confidence` score (0-100) and a doc-level `mean_confidence`. Use this when `obsidian_read_pdf` returns `has_text: false` (typical for scans, photographed paper, image-only PDFs).
12
+
13
+ - **Multilingual** via `lang` (default `'eng'`; multi-lang via `'+'`, e.g. `'eng+rus'` for English+Russian mixed scans). Trained-data files for each language download on first use into Tesseract's local cache (~10 MB per language).
14
+ - **Optional `pages` range** for partial OCR of long docs — OCR is the slowest step in the pipeline (~1-2s per page on M1 CPU), so a 100-page paper takes minutes.
15
+ - **Optional `scale`** (DPI multiplier, default 2 ~ 150 DPI, capped at 4 server-side to prevent adversarial-PDF OOM).
16
+ - **Per-page failure isolation** — one bad page doesn't sink the document.
17
+ - **Tesseract worker terminated after each call** so HTTP transport doesn't accumulate per-request state.
18
+
19
+ ### Added — two new optional dependencies
20
+
21
+ `tesseract.js@^7.0.0` (~1.4 MB unpacked, pure WebAssembly OCR engine) and `@napi-rs/canvas@^1.0.0` (~125 KB unpacked, native PDF→bitmap rendering with platform-specific binaries downloading conditionally) — both `optionalDependencies` so the markdown-only path stays zero-cost.
22
+
23
+ Lazy-imported via the same pattern as `pdfjs-dist` (v2.7.0), `better-sqlite3` (v1.x), and `@huggingface/transformers` (v2.0.0). Missing-deps surface a clean install-hint error rather than a cryptic module-not-found stack.
24
+
25
+ ### Server-side hardening
26
+
27
+ - `isEvalSupported: false`, `useSystemFonts: false`, `verbosity: 0` on pdfjs's `loadingTask` (matches v2.7.0 PDF read path).
28
+ - Render scale clamped to `[0.5, 4]` so adversarial PDFs claiming 100-DPI multipliers don't OOM the server.
29
+ - Tesseract worker terminated in a `finally` block so WebAssembly state never leaks even if a render or recognize call throws mid-page.
30
+ - Same path-safety + privacy filter (`--exclude-glob` / `--read-paths` / `vault.stat`) as `obsidian_read_note` and `obsidian_read_pdf`. Audit-tested at every read boundary.
31
+
32
+ ### Tests
33
+
34
+ 507 unit tests pass (was 502 in v2.9.0, +5 new):
35
+ - **ocrPdf path + privacy contract (+5):** rejects missing path arg, rejects non-existent file, refuses paths excluded by `--exclude-glob`, refuses paths outside `--read-paths` allowlist, accepts both `.pdf` and bare-stem paths consistently.
36
+
37
+ End-to-end OCR validation (loading a real Tesseract worker against a synthetic image-only PDF) is deferred to manual smoke — Tesseract.js + @napi-rs/canvas startup is heavy (~2s) and a real synthetic image-PDF fixture would inflate the test repo.
38
+
39
+ ### Surface delta vs v2.9.0
40
+
41
+ - **+1 read tool** (`obsidian_ocr_pdf`)
42
+ - **+2 optional deps** (`tesseract.js`, `@napi-rs/canvas`) — both lazy-loaded, markdown-only path zero-cost
43
+ - **Total surface:** 39 tools (28 always-on read + 1 opt-in `--persistent-index` + 3 opt-in diagnostic + 7 opt-in write) + 17 prompts
44
+
45
+ ### Migration
46
+
47
+ **No-op for default users.** OCR runs only when an agent explicitly calls `obsidian_ocr_pdf`. Existing `obsidian_read_pdf` behavior unchanged — it still returns `has_text: false` for scanned PDFs and now points at `obsidian_ocr_pdf` in its tool description.
48
+
49
+ Users on `--omit=optional` who try to call `obsidian_ocr_pdf` get a clean error message naming exactly what to install (`npm install tesseract.js @napi-rs/canvas`).
50
+
51
+ ### Strategic position
52
+
53
+ The PDF retrieval story is now complete:
54
+ - v2.7.0 — extraction tools (`obsidian_list_pdfs` / `obsidian_read_pdf`)
55
+ - v2.8.0 — blended into hybrid search (`obsidian_search` returns PDF chunks with `kind: "pdf"` + page citations)
56
+ - v2.9.0 — cross-encoder reranking on the blended candidate set
57
+ - **v2.10.0 — OCR for the image-only / scanned PDFs** the v2.8.0 pipeline previously skipped
58
+
59
+ **No other Obsidian-MCP currently does OCR for scanned PDFs.** Combined with the v2.0-v2.9 retrieval moats, enquire is now the only Obsidian-MCP that gives an agent searchable access to **every** PDF in your vault — text-PDFs, scanned-PDFs, multilingual content — with hybrid retrieval + cross-encoder reranking on top.
60
+
5
61
  ## [2.9.0] — 2026-05-08
6
62
 
7
63
  **Sprint 9 — BGE cross-encoder reranking on top of RRF.** Cross-encoder reranking is the SOTA technique in IR for boosting retrieval quality over bi-encoder candidates: after RRF fusion, the top-N hits are re-scored by a model that sees query+document interaction directly (instead of comparing pre-computed embeddings). Typical wins: +5-10 NDCG@10 on real-world retrieval. **No other Obsidian-MCP currently does cross-encoder reranking** — this extends our retrieval quality leadership claim.
package/README.md CHANGED
@@ -1,284 +1,265 @@
1
1
  <div align="center">
2
2
 
3
- <a href="https://github.com/oomkapwn/enquire-mcp"><img src="./assets/social-preview.png" alt="enquire — MCP server for Obsidian vaults. Hybrid retrieval (BM25 + TF-IDF + ML embeddings via RRF). Wikilinks, frontmatter, backlinks, Dataview, multilingual semantic search. For Claude Code, Cursor, Codex." width="100%"></a>
3
+ <a href="https://github.com/oomkapwn/enquire-mcp"><img src="./assets/social-preview.png" alt="enquire — MCP server for Obsidian vaults. Hybrid retrieval (BM25 + TF-IDF + ML embeddings via RRF + cross-encoder reranking). Wikilinks, frontmatter, backlinks, Dataview, multilingual semantic search, PDFs, remote MCP. For Claude Code, Cursor, Codex." width="100%"></a>
4
4
 
5
- # enquire — MCP server for Obsidian
5
+ # enquire — give your AI a search engine for your Obsidian vault
6
6
 
7
- **Hybrid retrieval (BM25 + TF-IDF + ML embeddings, RRF-fused) for your Obsidian vault.** Drop-in for Claude Code, Cursor, OpenClaw 🦞, Codex, Devin, and any MCP-compatible agent. Free. Multilingual (50+ languages). Offline-capable. No Obsidian plugin required.
7
+ **Hybrid retrieval. Cross-encoder reranking. PDFs. Multilingual. Remote MCP. Free.**
8
+
9
+ The most advanced Obsidian-MCP you can run today — drop into Claude Code, Claude.ai web, Cursor, ChatGPT, or any MCP client and your agent gets a single `obsidian_search` tool that fuses BM25 + TF-IDF + ML embeddings, reranks with a BGE cross-encoder, and surfaces blended markdown + PDF hits with page citations.
8
10
 
9
11
  [![CI](https://github.com/oomkapwn/enquire-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/oomkapwn/enquire-mcp/actions/workflows/ci.yml)
10
- [![npm](https://img.shields.io/npm/v/@oomkapwn/enquire-mcp/latest.svg?label=npm)](https://www.npmjs.com/package/@oomkapwn/enquire-mcp)
11
- [![beta](https://img.shields.io/npm/v/@oomkapwn/enquire-mcp/beta.svg?label=beta)](https://www.npmjs.com/package/@oomkapwn/enquire-mcp/v/beta)
12
- [![Tests](https://img.shields.io/badge/tests-408_passing-brightgreen.svg)](#engineering)
13
- [![License](https://img.shields.io/badge/license-MIT-yellow.svg)](./LICENSE)
14
- [![MCP](https://img.shields.io/badge/MCP-1.29-8A2BE2.svg)](https://modelcontextprotocol.io/)
12
+ [![npm](https://img.shields.io/npm/v/@oomkapwn/enquire-mcp/latest.svg?label=npm%20%40latest&color=cb3837)](https://www.npmjs.com/package/@oomkapwn/enquire-mcp)
13
+ [![tests](https://img.shields.io/badge/tests-502%20passing-brightgreen.svg)](#trust)
15
14
  [![SLSA-3](https://img.shields.io/badge/SLSA-3-blue.svg)](https://slsa.dev/spec/v1.0/levels#build-l3)
15
+ [![MCP](https://img.shields.io/badge/MCP-1.29-8A2BE2.svg)](https://modelcontextprotocol.io/)
16
+ [![License](https://img.shields.io/badge/license-MIT-yellow.svg)](./LICENSE)
17
+ [![Node](https://img.shields.io/badge/node-%E2%89%A520-3c873a.svg)](https://nodejs.org)
16
18
 
17
19
  </div>
18
20
 
21
+ ---
22
+
23
+ ## ⚡ 30-second quick start
24
+
19
25
  ```bash
20
26
  npm install -g @oomkapwn/enquire-mcp
21
27
  enquire-mcp serve --vault ~/Documents/Obsidian\ Vault
22
28
  ```
23
29
 
24
- That's it. Your AI now has structured access to wikilinks, backlinks, frontmatter, Dataview queries, and **`obsidian_search`** — a single hybrid-retrieval tool that auto-fuses BM25 + TF-IDF + ML embeddings.
30
+ That's it. Your AI now has structured access to wikilinks, backlinks, frontmatter, Dataview, and **`obsidian_search`** — the umbrella retrieval tool.
31
+
32
+ **For Claude Code / Cursor / Codex / any MCP client:**
33
+
34
+ ```json
35
+ {
36
+ "mcpServers": {
37
+ "obsidian": {
38
+ "command": "npx",
39
+ "args": ["-y", "@oomkapwn/enquire-mcp", "serve", "--vault", "/path/to/vault"]
40
+ }
41
+ }
42
+ }
43
+ ```
44
+
45
+ **Want hybrid retrieval at full power?** One-time setup, ~10 min for a 100-note vault:
46
+
47
+ ```bash
48
+ enquire-mcp install-model multilingual # ~120MB, 50+ languages
49
+ enquire-mcp build-embeddings --vault <path> # ~30ms/chunk on M1
50
+ # then: serve --persistent-index for BM25 + --enable-reranker for cross-encoder
51
+ ```
25
52
 
26
53
  ---
27
54
 
28
- ## Why enquire (vs alternatives)
55
+ ## 🎯 The only Obsidian-MCP with…
29
56
 
30
- | | Other Obsidian-MCPs | Smart Connections (paid plugin) | **enquire** |
31
- |---|:---:|:---:|:---:|
32
- | Read-only by default | varies | n/a | |
33
- | Resolves wikilinks (alias / section / block) | partial | n/a | ✅ full |
34
- | Backlinks ranked + snippeted | rare | n/a | |
35
- | Dataview-style queries | needs plugin | n/a | first-class |
36
- | Canvas (`.canvas`) read | rare | n/a | typed nodes + edges |
37
- | BM25 full-text search | rare | | FTS5 SQLite |
38
- | TF-IDF semantic search | ❌ | ❌ | ✅ |
39
- | **ML embeddings (multilingual)** | | paid | **free** |
40
- | **Hybrid (BM25+TF-IDF+embeddings, RRF)** | ❌ | ❌ | ✅ **only here** |
41
- | **PDFs blended into hybrid search** | ❌ | ❌ | ✅ **only here** (v2.8.0) |
42
- | **Cross-encoder reranking on top of RRF** | ❌ | ❌ | ✅ **only here** (v2.9.0) |
43
- | Per-signal observability on each hit | ❌ | ❌ | ✅ |
44
- | Privacy filter (`--exclude-glob` / `--read-paths`) | ❌ | n/a | ✅ verified at search + write paths |
45
- | Standalone (no Obsidian plugin) | varies | ❌ requires Obsidian | ✅ direct vault read |
46
- | MCP-native (any agent) | varies | ❌ Obsidian-only | ✅ stdio JSON-RPC |
47
- | **Remote MCP (HTTP transport, bearer auth)** | ❌ | ❌ | ✅ **only here** (v2.6.0) |
48
- | SLSA-3 provenance | ❌ | n/a | ✅ |
49
- | Test suite | rare | n/a | ✅ 502 unit tests |
57
+ - **Hybrid retrieval** (BM25 + TF-IDF + ML embeddings, RRF-fused)
58
+ - ✅ **Cross-encoder reranking** on top of RRF (+5-10 NDCG@10) — `v2.9.0`
59
+ - **PDFs blended into hybrid search** with `[page: N]` citation markers — `v2.8.0`
60
+ - **OCR for scanned / image-only PDFs** (Tesseract.js, multilingual) `v2.10.0`
61
+ - **Wikilink graph-boost** as a retrieval signal (1-step personalised PageRank seeded by RRF top-K)
62
+ - **Remote MCP** over HTTP with bearer auth + rate-limit + CORS — `v2.6.0`
63
+ - **Multilingual** semantic search (50+ languages, runs on CPU, free)
64
+ - **Note-tethered AI chat threads** persisted as markdown Smart Connections' #1 paid feature, free here
65
+
66
+ **Read-only by default.** All 7 write tools gated behind `--enable-write`. Privacy filter (`--exclude-glob` / `--read-paths`) verified at every search + write path. SLSA-3 release provenance.
50
67
 
51
68
  ---
52
69
 
53
- ## Architecture
70
+ ## 🏗️ How retrieval works
54
71
 
55
72
  ```mermaid
56
73
  graph LR
57
74
  Q[Query]
58
75
  Q --> S[obsidian_search]
59
- S --> BM25[BM25 / FTS5<br/>opt-in: --persistent-index]
76
+ S --> BM25[BM25 / FTS5<br/>--persistent-index]
60
77
  S --> TFIDF[TF-IDF<br/>always on]
61
- S --> EMB[ML embeddings<br/>opt-in: build-embeddings]
78
+ S --> EMB[ML embeddings<br/>build-embeddings]
62
79
  BM25 --> RRF{RRF fusion<br/>k=60}
63
80
  TFIDF --> RRF
64
81
  EMB --> RRF
65
- RRF --> R[Ranked hits<br/>per_signal observability]
82
+ RRF --> GB[Graph boost<br/>α × in-degree]
83
+ GB --> RR[Cross-encoder<br/>reranker<br/>--enable-reranker]
84
+ RR --> R[Ranked hits<br/>per_signal observability]
66
85
  ```
67
86
 
68
- `obsidian_search` auto-detects available signals and fuses them via Reciprocal Rank Fusion (Cormack et al, 2009). Returns per-signal contributions on every hit so agents can see WHY each result ranked.
87
+ `obsidian_search` auto-detects available signals and fuses them via Reciprocal Rank Fusion (Cormack et al, 2009). Wikilink graph-boost reranks top-K by 1-step personalised PageRank. Optional cross-encoder reranking (BGE) re-scores top-N for +5-10 NDCG@10. Every hit returns `per_signal` observability so you see WHY each result ranked.
69
88
 
70
- ```
71
- Tier 1: serve --vault <path> → TF-IDF (zero setup, instant)
72
- Tier 2: serve --vault <path> --persistent-index → + BM25 (sub-100ms top-10)
73
- Tier 3: + install-model + build-embeddings → + ML embeddings (multilingual)
74
- Tier 4: serve-http --bearer-token <token> → remote MCP (v2.6.0)
75
- same retrieval stack, exposed over HTTP for Claude.ai web,
76
- ChatGPT, Cursor HTTP, mobile. Tailscale Funnel / Cloudflare
77
- Tunnel for HTTPS. See docs/http-transport.md.
78
- ```
89
+ | Tier | Setup | What you get |
90
+ |---|---|---|
91
+ | **1** | `serve --vault <path>` | TF-IDF (zero setup, instant) |
92
+ | **2** | + `--persistent-index` | + BM25 (sub-100ms top-10) |
93
+ | **3** | + `install-model` + `build-embeddings` | + multilingual ML embeddings |
94
+ | **4** | + `--enable-reranker` | + BGE cross-encoder reranking |
95
+ | **5** | + `--include-pdfs` | + PDFs blended into all of the above |
96
+ | **6** | `serve-http --bearer-token …` | + remote MCP for Claude.ai web, ChatGPT, Cursor HTTP, mobile |
79
97
 
80
98
  ---
81
99
 
82
- ## Quick start
83
-
84
- > **Two channels:** `npm install @oomkapwn/enquire-mcp` → **v2.0** (stable, hybrid retrieval). `@beta` → preview track. `@1` → legacy v1.x line.
100
+ ## 🆚 vs alternatives
85
101
 
86
- **Claude Desktop / Claude Code / Cursor / Codex / any MCP client**:
102
+ | | Other Obsidian-MCPs | Smart Connections (paid) | **enquire** |
103
+ |---|:---:|:---:|:---:|
104
+ | Wikilinks (alias / section / block) | partial | n/a | ✅ full |
105
+ | Backlinks ranked + snippeted | rare | n/a | ✅ |
106
+ | Dataview-style queries | needs plugin | n/a | ✅ first-class |
107
+ | Canvas (`.canvas`) read | rare | n/a | ✅ typed nodes + edges |
108
+ | BM25 full-text | rare | ❌ | ✅ FTS5 SQLite |
109
+ | TF-IDF semantic | ❌ | ❌ | ✅ |
110
+ | ML embeddings (multilingual) | ❌ | 💰 paid | ✅ **free** |
111
+ | **Hybrid (BM25+TF-IDF+embeddings, RRF)** | ❌ | ❌ | ✅ **only here** |
112
+ | **Wikilink graph-boost retrieval signal** | ❌ | ❌ | ✅ **only here** |
113
+ | **PDFs blended into hybrid search** | ❌ | ❌ | ✅ **only here** |
114
+ | **OCR for scanned / image-only PDFs** | ❌ | ❌ | ✅ **only here** |
115
+ | **Cross-encoder reranking** | ❌ | ❌ | ✅ **only here** |
116
+ | **Remote MCP (HTTP + bearer auth)** | ❌ | ❌ | ✅ **only here** |
117
+ | Per-signal observability per hit | ❌ | ❌ | ✅ |
118
+ | Privacy filter (exclude/allow globs) | ❌ | n/a | ✅ verified at search + write paths |
119
+ | Standalone (no Obsidian plugin) | varies | ❌ requires Obsidian | ✅ direct vault read |
120
+ | MCP-native (any agent) | varies | ❌ Obsidian-only | ✅ stdio + HTTP |
121
+ | SLSA-3 release provenance | ❌ | n/a | ✅ |
122
+ | Test suite | rare | n/a | ✅ 507 unit tests |
87
123
 
88
- ```json
89
- {
90
- "mcpServers": {
91
- "obsidian": {
92
- "command": "npx",
93
- "args": ["-y", "@oomkapwn/enquire-mcp", "serve", "--vault", "/path/to/vault"]
94
- }
95
- }
96
- }
97
- ```
124
+ > **Strategic claim:** enquire is the open-source backend for [Karpathy-style LLM Wikis](https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f) on top of your existing Obsidian vault. The `vault_synth` / `vault_wiki_compile` / `vault_lint_extended` prompts implement the ingest → query → lint → compile workflow natively over `.md` + `[[wikilinks]]`. Knowledge that compounds, traceable to sources.
98
125
 
99
- | Client | Config file |
100
- |---|---|
101
- | Claude Desktop | macOS `~/Library/Application Support/Claude/claude_desktop_config.json` · Windows `%APPDATA%\Claude\claude_desktop_config.json` |
102
- | Claude Code (CLI) | `~/.claude.json` (global) or `.mcp.json` (per-project) |
103
- | Cursor | `~/.cursor/mcp.json` (global) or `.cursor/mcp.json` (per-project) |
104
- | Codex / OpenClaw / Devin | per-tool MCP config |
126
+ ---
105
127
 
106
- **Enable hybrid retrieval** (one-time setup, ~10 min for a 100-note vault):
128
+ ## 🛠️ All 39 tools at a glance
107
129
 
108
- ```bash
109
- enquire-mcp install-model multilingual # ~120MB, 50+ languages
110
- enquire-mcp build-embeddings --vault <path> # ~30ms/chunk on M1
111
- # Add --persistent-index to your serve invocation for BM25.
112
- ```
130
+ The umbrella `obsidian_search` plus 38 specialized tools for wikilinks, backlinks, Dataview, frontmatter, canvas, PDFs, OCR, vault stats, graph navigation, and writes.
113
131
 
114
- **Remote MCP** (v2.6.0 — Claude.ai web, ChatGPT, Cursor HTTP, mobile):
132
+ <details>
133
+ <summary><b>28 always-on read tools</b> — click to expand</summary>
115
134
 
116
- ```bash
117
- enquire-mcp gen-token > ~/.enquire/token # 256-bit bearer
118
- enquire-mcp serve-http \
119
- --vault ~/Obsidian \
120
- --bearer-token "$(cat ~/.enquire/token)" \
121
- --persistent-index
122
- # Front with Tailscale Funnel / Cloudflare Tunnel for HTTPS — see
123
- # docs/http-transport.md.
124
- ```
135
+ `obsidian_search` · `obsidian_context_pack` · `obsidian_chat_thread_read` · `obsidian_frontmatter_get` · `obsidian_frontmatter_search` · `obsidian_read_note` · `obsidian_list_notes` · `obsidian_resolve_wikilink` · `obsidian_get_backlinks` · `obsidian_get_outbound_links` · `obsidian_get_unresolved_wikilinks` · `obsidian_get_recent_edits` · `obsidian_list_tags` · `obsidian_dataview_query` · `obsidian_find_path` · `obsidian_find_similar` · `obsidian_get_note_neighbors` · `obsidian_stats` · `obsidian_lint_wiki` · `obsidian_open_questions` · `obsidian_paper_audit` · `obsidian_validate_note_proposal` · `obsidian_list_canvases` · `obsidian_read_canvas` · `obsidian_list_pdfs` · `obsidian_read_pdf` · `obsidian_ocr_pdf` · `obsidian_open_in_ui`
125
136
 
126
- No other Obsidian-MCP currently ships a remote-HTTP transport. Same vault, same tools, same hybrid retrieval — just over HTTPS instead of stdio.
137
+ </details>
127
138
 
128
- ---
139
+ <details>
140
+ <summary><b>4 opt-in read tools</b> (diagnostic single-rankers) — click to expand</summary>
129
141
 
130
- ## Tools (38 total)
142
+ `obsidian_full_text_search` (`--persistent-index`) · `obsidian_search_text` · `obsidian_semantic_search` · `obsidian_embeddings_search` (all 3 require `--diagnostic-search-tools`)
131
143
 
132
- ### 27 always-on read tools
144
+ </details>
133
145
 
134
- | Tool | What it does |
135
- |---|---|
136
- | `obsidian_search` | **Hybrid retrieval** — fuses BM25 + TF-IDF + ML embeddings via RRF. The default search tool. Auto-detects available signals. v2.2.0: `granularity: "block"` arg returns chunks instead of notes. **v2.8.0:** with `--include-pdfs`, PDF chunks blend in alongside markdown — every hit carries `kind: "md" \| "pdf"` and PDF snippets include `[page: N]` markers for citation. **v2.9.0:** with `--enable-reranker`, top-N RRF candidates are re-scored by a BGE cross-encoder for +5-10 NDCG@10 typical retrieval quality. |
137
- | `obsidian_context_pack` | **v2.2.0.** Token-budgeted context bundling: takes a question, runs hybrid search, gathers note bodies + backlinks + optionally recent dailies, returns one ready-to-paste markdown bundle. Saves ~5 tool calls. |
138
- | `obsidian_chat_thread_read` | **v2.2.0.** Parse a note's `## Chat: <title>` block into structured messages (role/timestamp/content/line-range). Pair with `_append` (write) for note-tethered AI conversations. |
139
- | `obsidian_frontmatter_get` | **v2.3.0.** Read parsed YAML frontmatter for a note. With `key`, returns just that field. |
140
- | `obsidian_frontmatter_search` | **v2.3.0.** Find notes by frontmatter predicate (`equals` / `exists` / `contains`). Useful as a precursor to bulk `_set`. |
141
- | `obsidian_read_note` | Full content + frontmatter + wikilinks + embeds + tags. Also accepts periodic-note aliases (`title: "today"` / `"weekly"` / `"monthly"`). |
142
- | `obsidian_list_notes` | Vault-wide or folder-scoped. Includes title + tags + mtime + counts. |
143
- | `obsidian_resolve_wikilink` | Resolves `[[Note]]`, `[[Note\|Alias]]`, `[[Note#Section]]`, `[[Note#^block]]`, with did-you-mean on near-miss. |
144
- | `obsidian_get_backlinks` | Every note linking to X, ranked + snippeted. |
145
- | `obsidian_get_outbound_links` | Outbound `[[wikilinks]]` from one note, with resolution status. |
146
- | `obsidian_get_unresolved_wikilinks` | Vault-wide broken-link audit. |
147
- | `obsidian_get_recent_edits` | Notes modified in the last N minutes. |
148
- | `obsidian_list_tags` | Tag census across vault (frontmatter + inline). |
149
- | `obsidian_dataview_query` | First-class Dataview-style queries (`LIST` / `TABLE`, `WHERE`, `AND` / `OR` / `LIKE` / `contains`). |
150
- | `obsidian_find_path` | Multi-hop graph BFS between two notes (with alternatives). |
151
- | `obsidian_find_similar` | Note-to-note similarity (tag + folder + content signals). |
152
- | `obsidian_get_note_neighbors` | Outbound + inbound + tag-sibling for one note. |
153
- | `obsidian_stats` | Vault dashboard: note count, tag count, broken links. |
154
- | `obsidian_lint_wiki` | Karpathy LLM-Wiki `/lint` workflow (orphans / broken / stubs / stale). |
155
- | `obsidian_open_questions` | Find open `?` questions across the vault. |
156
- | `obsidian_paper_audit` | Track arXiv references and read-status. |
157
- | `obsidian_validate_note_proposal` | Lint a draft note before writing (closes the #1 LLM-write pain). |
158
- | `obsidian_list_canvases` | List `.canvas` files with node + edge counts. |
159
- | `obsidian_read_canvas` | Parse `.canvas` into typed nodes (text/file/link/group) + edges. |
160
- | `obsidian_list_pdfs` | **v2.7.0.** List `.pdf` files with size + mtime. PDFs are the #1 non-markdown content kind in real vaults; no other Obsidian-MCP indexes them. |
161
- | `obsidian_read_pdf` | **v2.7.0.** Extract page-by-page text + doc metadata (title/author/etc) from a PDF. Optional 1-indexed page-range slice. Image-only / scanned PDFs surface `has_text: false`. Powered by Mozilla PDF.js (Apache-2.0, pinned to `optionalDependencies` so the markdown-only path stays zero-cost). |
162
- | `obsidian_open_in_ui` | Emit `obsidian://open?vault=...` URI. |
163
-
164
- ### 4 opt-in read tools
165
-
166
- | Tool | Flag | Notes |
167
- |---|---|---|
168
- | `obsidian_full_text_search` | `--persistent-index` + `--diagnostic-search-tools` | BM25 ranking, sub-100ms. |
169
- | `obsidian_search_text` | `--diagnostic-search-tools` | Token search (all/any/phrase modes). |
170
- | `obsidian_semantic_search` | `--diagnostic-search-tools` | TF-IDF cosine standalone. |
171
- | `obsidian_embeddings_search` | `--diagnostic-search-tools` | ML embeddings standalone. |
146
+ <details>
147
+ <summary><b>7 opt-in write tools</b> (require <code>--enable-write</code>) — click to expand</summary>
172
148
 
173
- ### 7 opt-in write tools (`--enable-write`)
149
+ `obsidian_create_note` · `obsidian_append_to_note` · `obsidian_rename_note` (rewrites every wikilink across vault, code-fence-aware) · `obsidian_replace_in_notes` (bulk find/replace) · `obsidian_archive_note` · `obsidian_chat_thread_append` · `obsidian_frontmatter_set` (atomic YAML manipulation, `dry_run` supported)
174
150
 
175
- | Tool | Notes |
176
- |---|---|
177
- | `obsidian_create_note` | Refuses overwrite without `overwrite=true`. Empty path rejected. |
178
- | `obsidian_append_to_note` | Symlink-safe; respects `--max-file-bytes`. |
179
- | `obsidian_rename_note` | Rewrites every wikilink across vault. Code-fence-aware. `dry_run` available. |
180
- | `obsidian_replace_in_notes` | Bulk find/replace. Per-file errors collected; `partial: true` on mid-loop fail. |
181
- | `obsidian_archive_note` | Wraps rename to `Archive/`. Preserves backlinks. |
182
- | `obsidian_chat_thread_append` | **v2.2.0.** Append a user/assistant/system message to a note's `## Chat:` block. Creates note + heading if absent. Threads stored as markdown — searchable, version-controllable, survive sessions. |
183
- | `obsidian_frontmatter_set` | **v2.3.0.** Surgical YAML manipulation — set/unset keys on a note atomically. Pass `null` as value to delete. Round-trips through gray-matter so YAML formatting stays consistent. `dry_run` supported. |
151
+ </details>
184
152
 
185
- Plus **2 + 1 opt-in MCP resources** (`obsidian://note/...`, `obsidian://vault-info`, `obsidian://chunk/...`) and **17 MCP prompts** (`summarize_recent_edits`, `weekly_review`, `monthly_review`, `find_orphans`, `extract_todos`, `process_inbox`, `review_tag`, `consolidate_tags`, `find_duplicates`, `lint_wiki`, `search_with_query_expansion`, `vault_synth`, `vault_wiki_compile`, `vault_lint_extended`, `vault_capture`, `vault_persona_search`, `vault_automation_setup`).
153
+ **Plus:** 2 + 1 opt-in MCP resources, and **17 MCP prompts** (`summarize_recent_edits`, `weekly_review`, `monthly_review`, `find_orphans`, `extract_todos`, `process_inbox`, `review_tag`, `consolidate_tags`, `find_duplicates`, `lint_wiki`, `search_with_query_expansion`, `vault_synth`, `vault_wiki_compile`, `vault_lint_extended`, `vault_capture`, `vault_persona_search`, `vault_automation_setup`).
186
154
 
187
- > **v2.4.0 strategic position:** enquire-mcp is the open-source backend for **Karpathy-style LLM Wikis** on top of your existing Obsidian vault. The `vault_synth` + `vault_wiki_compile` + `vault_lint_extended` prompts implement the [Karpathy LLM-Wiki workflow](https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f) (ingest → query → lint → compile) natively over Obsidian's `.md` + `[[wikilinks]]` substrate. Knowledge that compounds, traceable to sources.
155
+ 📖 Full reference: **[docs/api.md](./docs/api.md)** · Remote-MCP deployment guide: **[docs/http-transport.md](./docs/http-transport.md)**
188
156
 
189
157
  ---
190
158
 
191
- ## Configuration
159
+ ## ⚙️ Configuration
160
+
161
+ The flags you'll actually use:
192
162
 
193
- | Flag | Default | Notes |
163
+ | Flag | Default | What it does |
194
164
  |---|---|---|
195
- | `--vault <path>` | required | Path to Obsidian vault root. |
196
- | `--enable-write` | off | Register the 5 write tools. |
197
- | `--exclude-glob <pat...>` | none | Privacy denylist. Repeatable. Example: `'02_Personal/**'`. |
198
- | `--read-paths <pat...>` | none | Privacy allowlist (only matching paths visible). Repeatable. |
199
- | `--persistent-index` | off | SQLite FTS5 BM25. Enables `obsidian_full_text_search` (with `--diagnostic-search-tools`). |
200
- | `--persistent-cache` | off | Persist parsed-note cache across restarts. |
201
- | `--watch` | off | Live invalidation on `.md` add/change/unlink. |
202
- | `--diagnostic-search-tools` | off | Register 4 single-ranker search tools (defaults: hybrid `obsidian_search` only). |
203
- | `--enabled-tools <name...>` | all | Strict allowlist (gate to a subset). |
204
- | `--disabled-tools <name...>` | none | Denylist. |
205
- | `--max-file-bytes <n>` | 5 MB | Per-file read/write cap. |
206
- | `--cache-size <n>` | 1024 | LRU cap for parsed-note cache. |
207
-
208
- Subcommands: `serve` · `serve-http` (v2.6.0 — remote MCP) · `gen-token` (v2.6.0) · `clear-cache` · `clear-index` · `index` (cold-build FTS5) · `install-model` · `build-embeddings` · `clear-embeddings`.
209
-
210
- Full reference: [docs/api.md](./docs/api.md). Remote-MCP deployment guide: [docs/http-transport.md](./docs/http-transport.md).
165
+ | `--vault <path>` | required | Path to Obsidian vault root |
166
+ | `--persistent-index` | off | SQLite FTS5 BM25, sub-100ms top-10 |
167
+ | `--include-pdfs` | off | Index PDFs into FTS5 + embeddings |
168
+ | `--enable-reranker` | off | BGE cross-encoder reranking on RRF top-N |
169
+ | `--enable-write` | off | Register the 7 write tools |
170
+ | `--exclude-glob <pat...>` | none | Privacy denylist (e.g. `'02_Personal/**'`) |
171
+ | `--read-paths <pat...>` | none | Privacy allowlist (only matching paths visible) |
172
+ | `--watch` | off | Live invalidation on `.md` add/change/unlink |
173
+ | `--persistent-cache` | off | Survive cold starts |
174
+
175
+ Subcommands: `serve` · `serve-http` · `gen-token` · `clear-cache` · `clear-index` · `clear-embeddings` · `index` · `install-model` · `build-embeddings`.
176
+
177
+ **Remote MCP** for Claude.ai web / ChatGPT / Cursor HTTP / mobile:
178
+
179
+ ```bash
180
+ enquire-mcp gen-token > ~/.enquire/token # one-time
181
+ enquire-mcp serve-http \
182
+ --vault ~/Obsidian \
183
+ --bearer-token "$(cat ~/.enquire/token)" \
184
+ --persistent-index --include-pdfs --enable-reranker
185
+ # Front with Tailscale Funnel / Cloudflare Tunnel for HTTPS.
186
+ ```
211
187
 
212
188
  ---
213
189
 
214
- ## Security
190
+ ## 🛡️ Trust
215
191
 
216
- - **Read-only by default.** Write tools require `--enable-write`.
217
- - **Path traversal blocked.** Realpath check on every read+write target. Symlinks inside the vault that resolve outside are rejected.
218
- - **Privacy boundary verified across all paths**, including persistent indexes (FTS5 / embed-db search-time filter) and the `obsidian://chunk/...` resource. Privacy fail-closed: empty `--read-paths` / `--exclude-glob` patterns refuse to start.
192
+ - **Read-only by default.** Every write tool requires `--enable-write`.
193
+ - **Path traversal blocked.** Realpath check on every read+write target. Symlinks resolving outside the vault are rejected.
194
+ - **Privacy boundary verified across all paths** including persistent FTS5 + embed indexes and the `obsidian://chunk/...` resource. Privacy fail-closed: empty `--read-paths` / `--exclude-glob` patterns refuse to start.
195
+ - **HTTP transport hardened.** Bearer auth (constant-time SHA-256 + `timingSafeEqual`), per-token sliding rate-limit, strict CORS allowlist with credential-leak guard.
219
196
  - **`gray-matter` (`js-yaml` safeLoad)** — no code execution via frontmatter.
220
- - **DQL parser** — no shell, no `eval`, no template expansion.
221
197
  - **Cache + index files** — chmod 0600, parent dir 0700.
222
198
  - **SLSA-3 provenance** on every npm release.
223
- - **Branch protection ruleset** with `bypass_mode: pull_request` — every change goes through PR with audit trail. Release pipeline verifies SHA-on-main + 8 required CI checks before publishing.
224
-
225
- Full posture: [SECURITY.md](./SECURITY.md). Report vulnerabilities to `oomkapwn@gmail.com`.
226
-
227
- ---
228
-
229
- ## Engineering
199
+ - **Branch protection** with `bypass_mode: pull_request` — every change goes through PR review. Release pipeline verifies tagged SHA is on `main` AND all 8 required CI checks reported `success` on it.
230
200
 
231
201
  | Surface | Posture |
232
202
  |---|---|
233
- | Language | TypeScript strict + `noUncheckedIndexedAccess` |
234
- | Lint | Biome 2 (zero-warning policy) |
235
- | Tests | 502 unit tests across 24 files |
236
- | CI | ubuntu × {Node 20, 22, 24} required + macOS advisory job |
203
+ | Tests | 507 unit tests across 25 files, 8 required CI gates per PR |
237
204
  | Coverage | Lines ≥86%, statements ≥82%, functions ≥75%, branches ≥73% (gated) |
238
205
  | Audit | `npm audit --audit-level=moderate` for prod; high for dev |
239
- | Runtime deps | 5 mandatory (`@modelcontextprotocol/sdk`, `chokidar`, `commander`, `gray-matter`, `zod`) + 2 optional (`better-sqlite3` for FTS5 / embed-db; `@huggingface/transformers` for ML embeddings) |
240
- | Releases | npm + GitHub release per tag · semver · provenance attached · channels: `latest` (stable), `beta`, `alpha` |
206
+ | CI | Ubuntu × {Node 20, 22, 24} required + macOS advisory job |
207
+ | Lint | Biome 2 (zero-warning policy) |
208
+ | Language | TypeScript strict + `noUncheckedIndexedAccess` |
209
+ | Runtime deps | 5 mandatory, 3 optional (FTS5 + ML embeddings + PDF parser — markdown-only path stays zero-cost) |
210
+ | Releases | npm + GitHub release per tag · semver · SLSA-3 provenance |
241
211
 
242
- ```bash
243
- git clone https://github.com/oomkapwn/enquire-mcp.git
244
- cd enquire-mcp && npm install
245
- npm test # full suite
246
- npm run lint # zero warnings
247
- npm run build # tsc → dist/
248
- ```
212
+ Full posture: **[SECURITY.md](./SECURITY.md)**. Report vulnerabilities to `oomkapwn@gmail.com`.
249
213
 
250
214
  ---
251
215
 
252
- ## FAQ
216
+ ## FAQ
217
+
218
+ **Do I need Obsidian installed?**
219
+ No. enquire reads `.md` + `.canvas` + `.pdf` files directly. Works against any Obsidian-format vault.
220
+
221
+ **Will this write to my vault?**
222
+ Not unless you start with `--enable-write`. Even then, all 7 write tools are gated by privacy filters and refuse to overwrite without `overwrite: true`. `dry_run` modes available on the destructive ones.
223
+
224
+ **Is my data sent anywhere?**
225
+ Only on `enquire-mcp install-model` (downloads ONNX weights from HuggingFace, one-time). Serve mode itself never makes outbound HTTP. Embeddings + reranker run on CPU locally.
226
+
227
+ **How is this different from Smart Connections?**
228
+ Smart Connections is a paid Obsidian plugin that runs ML embeddings inside Obsidian. enquire is a standalone MCP server: free, MCP-native (works with Claude / Cursor / Codex / any agent), and fuses 3 retrieval signals + cross-encoder reranking for higher recall + precision than embeddings alone. PDFs index too.
229
+
230
+ **Performance on large vaults?**
231
+ Cold-build of FTS5 on a 1k-note vault: ~5s. Warm BM25 top-10: sub-100ms. Embedding build: ~30ms/chunk on M1 (~8min for 8k chunks). Hybrid query latency: <200ms typical. Reranker adds ~30-50ms at top-50. Maintainer dogfoods on a 128-note bilingual vault with all of the above on.
253
232
 
254
- **Q: Do I need Obsidian installed?**
255
- No. enquire reads `.md` + `.canvas` files directly. Works against any Obsidian-format vault.
233
+ **Languages?**
234
+ Default embedding model is `paraphrase-multilingual-MiniLM-L12-v2` (50+ languages). Multilingual cross-encoder reranker (`mxbai-rerank-xsmall-v1`) is the default too. Validated end-to-end on Russian + English bilingual vaults. CJK / Thai / Khmer / Lao tokenization via `Intl.Segmenter` (Node 16+ ICU).
256
235
 
257
- **Q: Will this write to my vault?**
258
- No, unless you explicitly start with `--enable-write`. Even then, all 5 write tools are gated by privacy filters and refuse to overwrite without `overwrite: true`.
236
+ **Can I run it remotely?**
237
+ Yes. `serve-http` exposes the same server over [Streamable HTTP](https://modelcontextprotocol.io/specification/2025-06-18/basic/transports#streamable-http) with bearer auth. Front with Tailscale Funnel or Cloudflare Tunnel for HTTPS works with claude.ai web, ChatGPT custom GPT, Cursor HTTP mode, mobile MCP clients. See [docs/http-transport.md](./docs/http-transport.md).
259
238
 
260
- **Q: Is my data sent anywhere?**
261
- Only on `enquire-mcp install-model` (downloads ONNX weights from HuggingFace). The serve mode itself never makes outbound HTTP. Embeddings run on CPU locally.
239
+ ---
262
240
 
263
- **Q: How is this different from Smart Connections?**
264
- Smart Connections is a paid Obsidian plugin that does ML embeddings inside Obsidian. enquire-mcp is a standalone MCP server: free, MCP-native (works with Claude / Cursor / Codex / any agent), and adds hybrid retrieval (BM25 + TF-IDF + embeddings via RRF) for higher recall than embeddings alone.
241
+ ## 🚀 Releases
265
242
 
266
- **Q: Performance on large vaults?**
267
- Cold-build of FTS5 on a 1k-note vault: ~5s. Warm BM25 top-10: sub-100ms. Embedding build: ~30ms/chunk on M1 (8s for 256 chunks, ~8min for 8k chunks). Hybrid query latency: <200ms typical.
243
+ `v2.0.0` (stable) · `v2.5.0` (5-sprint roadmap consolidated) · `v2.6.0` (remote MCP) · `v2.7.0` (PDF read tools) · `v2.8.0` (PDFs blended into hybrid search) · `v2.9.0` (BGE cross-encoder reranking)
268
244
 
269
- **Q: Does it support languages other than English?**
270
- Yes. Default embedding model is `paraphrase-multilingual-MiniLM-L12-v2` (50+ languages). Validated end-to-end on Russian + English bilingual vaults. CJK requires the upcoming Intl.Segmenter pre-pass (v2.1 backlog).
245
+ Channel: `npm install @oomkapwn/enquire-mcp` latest stable. Full changelog: [CHANGELOG.md](./CHANGELOG.md).
271
246
 
272
247
  ---
273
248
 
274
- ## Contributing
249
+ ## 🤝 Contributing
275
250
 
276
- Issues and PRs welcome. Read [CONTRIBUTING.md](./CONTRIBUTING.md). All changes go through PR review per branch protection ruleset.
251
+ ```bash
252
+ git clone https://github.com/oomkapwn/enquire-mcp.git
253
+ cd enquire-mcp && npm install
254
+ npm test # full suite (502 tests, ~5s)
255
+ npm run lint # zero warnings
256
+ npm run build # tsc → dist/
257
+ ```
277
258
 
278
- ## License & credits
259
+ Issues, PRs, and ideas welcome. Branch protection requires PR review on `main`.
279
260
 
280
- [MIT](./LICENSE). Built by Alex — [GitHub `@oomkapwn`](https://github.com/oomkapwn) · [X `@OomkaBear`](https://x.com/OomkaBear).
261
+ ---
281
262
 
282
- Named after [ENQUIRE](https://en.wikipedia.org/wiki/ENQUIRE) Tim Berners-Lee's 1980 hypertext prototype of the World Wide Web.
263
+ ## 📜 License & credits
283
264
 
284
- > **Not affiliated with Obsidian.md.** Obsidian and the Obsidian logo are trademarks of Dynalist Inc. enquire-mcp is an independent open-source project that reads Obsidian-format vaults.
265
+ MIT. Built by [Alex (@OomkaBear)](https://github.com/oomkapwn). Named after [Tim Berners-Lee's 1980 prototype of the WWW](https://en.wikipedia.org/wiki/ENQUIRE) the original hypertext system, before the web. The original spec was that you could ask the system anything; this brings that to your vault.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAIA,OAAO,EAAE,SAAS,EAAoB,MAAM,yCAAyC,CAAC;AAMtF,OAAO,EAAkC,QAAQ,EAAE,MAAM,WAAW,CAAC;AAwCrE,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAW5C,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;IACnC,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC;;mCAE+B;IAC/B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB;8EAC0E;IAC1E,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,qEAAqE;IACrE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,qEAAqE;IACrE,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAcD,iBAAe,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAsVnC;AAED;;;;;;;;;;GAUG;AACH,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,KAAK,CAAC;IACb,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC1B,OAAO,EAAE,YAAY,GAAG,IAAI,CAAC;IAC7B,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC3B,YAAY,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC1B,cAAc,EAAE;QAAE,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC;CACtC;AAED;;;;;GAKG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,UAAU,CAAC,CAkE/E;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,YAAY,GAAG,SAAS,CA8F9E;AAED,iBAAe,WAAW,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAoD5D;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,CAY1D;AAo7DD,iBAAS,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAM3D;AAsCD,OAAO,EAAE,IAAI,EAAE,gBAAgB,EAAE,WAAW,EAAE,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAIA,OAAO,EAAE,SAAS,EAAoB,MAAM,yCAAyC,CAAC;AAMtF,OAAO,EAAkC,QAAQ,EAAE,MAAM,WAAW,CAAC;AAyCrE,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAW5C,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;IACnC,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC;;mCAE+B;IAC/B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB;8EAC0E;IAC1E,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,qEAAqE;IACrE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,qEAAqE;IACrE,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAcD,iBAAe,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAsVnC;AAED;;;;;;;;;;GAUG;AACH,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,KAAK,CAAC;IACb,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC1B,OAAO,EAAE,YAAY,GAAG,IAAI,CAAC;IAC7B,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC3B,YAAY,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC1B,cAAc,EAAE;QAAE,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC;CACtC;AAED;;;;;GAKG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,UAAU,CAAC,CAkE/E;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,YAAY,GAAG,SAAS,CA8F9E;AAED,iBAAe,WAAW,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAoD5D;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,CAY1D;AAy9DD,iBAAS,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAM3D;AAsCD,OAAO,EAAE,IAAI,EAAE,gBAAgB,EAAE,WAAW,EAAE,CAAC"}
package/dist/index.js CHANGED
@@ -9,10 +9,10 @@ import { z } from "zod";
9
9
  import { EmbedDb } from "./embed-db.js";
10
10
  import { DEFAULT_MODEL_ALIAS, EMBEDDING_MODELS, loadEmbedder, resolveModel } from "./embeddings.js";
11
11
  import { chunkContent, defaultIndexFile, FtsIndex } from "./fts5.js";
12
- import { appendToNote, archiveNote, chatThreadAppend, chatThreadRead, contextPack, createNote, dataviewQuery, embeddingsSearch, findPath, findSimilar, frontmatterGet, frontmatterSearch, frontmatterSet, getBacklinks, getNoteNeighbors, getOpenQuestions, getOutboundLinks, getRecentEdits, getUnresolvedWikilinks, getVaultStats, lintWiki, listCanvases, listNotes, listPdfs, listTags, openInUi, paperAudit, readCanvas, readNote, readPdf, renameNote, replaceInNotes, resolveWikilink, searchHybrid, searchText, semanticSearch, validateNoteProposal } from "./tools.js";
12
+ import { appendToNote, archiveNote, chatThreadAppend, chatThreadRead, contextPack, createNote, dataviewQuery, embeddingsSearch, findPath, findSimilar, frontmatterGet, frontmatterSearch, frontmatterSet, getBacklinks, getNoteNeighbors, getOpenQuestions, getOutboundLinks, getRecentEdits, getUnresolvedWikilinks, getVaultStats, lintWiki, listCanvases, listNotes, listPdfs, listTags, ocrPdf, openInUi, paperAudit, readCanvas, readNote, readPdf, renameNote, replaceInNotes, resolveWikilink, searchHybrid, searchText, semanticSearch, validateNoteProposal } from "./tools.js";
13
13
  import { Vault } from "./vault.js";
14
14
  import { VaultWatcher } from "./watcher.js";
15
- const VERSION = "2.9.0";
15
+ const VERSION = "2.10.0";
16
16
  /** Default location for the persistent embedding index, alongside .fts5.db. */
17
17
  function embedDbPath(vaultRoot) {
18
18
  // Match the FTS5 location convention by stripping the .fts5.db extension
@@ -1148,7 +1148,7 @@ rerankerConfig = null) {
1148
1148
  }, async (args) => textResult(await listPdfs(vault, args)));
1149
1149
  server.registerTool("obsidian_read_pdf", {
1150
1150
  title: "Extract text from a PDF (page-by-page)",
1151
- description: "Extracts plain text from one PDF, returning per-page text + a `full_text` join + doc-level metadata (title/author/subject/etc). Image-only / scanned PDFs surface `has_text: false` so agents can detect-and-recommend OCR. Optional `pages` slice (1-indexed inclusive range) for partial reads of long documents. Read-only. Same path-safety + privacy filter as `obsidian_read_note`. Powered by Mozilla's PDF.js (Apache-2.0).",
1151
+ description: "Extracts plain text from one PDF, returning per-page text + a `full_text` join + doc-level metadata (title/author/subject/etc). Image-only / scanned PDFs surface `has_text: false` so agents can detect-and-recommend OCR via `obsidian_ocr_pdf` (v2.10.0). Optional `pages` slice (1-indexed inclusive range) for partial reads of long documents. Read-only. Same path-safety + privacy filter as `obsidian_read_note`. Powered by Mozilla's PDF.js (Apache-2.0).",
1152
1152
  annotations: { ...READ_ONLY, title: "Read PDF" },
1153
1153
  inputSchema: {
1154
1154
  path: z.string().describe("Vault-relative path of the .pdf file (with or without .pdf)"),
@@ -1159,6 +1159,33 @@ rerankerConfig = null) {
1159
1159
  include_metadata: z.boolean().optional().describe("Include doc-level metadata in result (default true)")
1160
1160
  }
1161
1161
  }, async (args) => textResult(await readPdf(vault, args)));
1162
+ // v2.10.0 — OCR for image-only / scanned PDFs. Completes the v2.7-v2.8
1163
+ // PDF retrieval story: when `obsidian_read_pdf` returns `has_text: false`,
1164
+ // the agent calls `obsidian_ocr_pdf` to extract text via Tesseract.js.
1165
+ // Tesseract.js + @napi-rs/canvas are optionalDependencies — clean
1166
+ // install-hint error if missing. ~1-2s per page on M1 CPU.
1167
+ server.registerTool("obsidian_ocr_pdf", {
1168
+ title: "OCR a scanned/image-only PDF (Tesseract.js)",
1169
+ description: "Runs Tesseract OCR over each page of an image-only / scanned PDF, returning per-page text + per-page confidence + mean confidence + the same shape as `obsidian_read_pdf`. Use this when `obsidian_read_pdf` returns `has_text: false` (typical for scans, photographed paper, image-only PDFs). Multilingual via `lang` (default `'eng'`; multi-lang via `'+'`, e.g. `'eng+rus'`). Optional `pages` range and `scale` (DPI multiplier, default 2 ~ 150 DPI, capped at 4). ~1-2s per page on M1 CPU. Read-only. Powered by Tesseract.js (Apache-2.0; trained-data files download on first use into the local cache, ~10 MB per language) + @napi-rs/canvas for PDF→bitmap rendering. Both gated to `optionalDependencies` so the markdown-only path stays zero-cost.",
1170
+ annotations: { ...READ_ONLY, title: "OCR PDF" },
1171
+ inputSchema: {
1172
+ path: z.string().describe("Vault-relative path of the .pdf file (with or without .pdf)"),
1173
+ lang: z
1174
+ .string()
1175
+ .optional()
1176
+ .describe("Tesseract language pack(s). Default 'eng'. Multi-lang via '+': 'eng+rus' for English+Russian mixed scans. Common: 'eng', 'rus', 'jpn', 'chi_sim', 'fra', 'deu'."),
1177
+ pages: z
1178
+ .tuple([z.number().int().positive(), z.number().int().positive()])
1179
+ .optional()
1180
+ .describe("Optional 1-indexed inclusive page range, e.g. [2, 5] OCRs pages 2..5"),
1181
+ scale: z
1182
+ .number()
1183
+ .min(0.5)
1184
+ .max(4)
1185
+ .optional()
1186
+ .describe("Render scale (DPI multiplier). Default 2 (~150 DPI). Higher = better OCR on small text but slower.")
1187
+ }
1188
+ }, async (args) => textResult(await ocrPdf(vault, args)));
1162
1189
  // v2.0.0-beta.3: gated — see comment on obsidian_search_text above.
1163
1190
  if (diagnosticSearchTools)
1164
1191
  server.registerTool("obsidian_semantic_search", {