Skip to content

4.0

4.0 is the release where the vault stops being just a search index. It gains awareness of OKF (Google's Open Knowledge Format): detection, filtering, a conformance audit, migration and bundle-export tooling, and an operator-gated enforced-write layer with human-in-the-loop attestation. The summarize tool grows a map-reduce path for folders too large for one model call, dual-mode background jobs, and a client-side alternative for installs with no configured backend. Search gains configurable title fields, searchable frontmatter, and per-folder and per-column ranking weights. The Claude Code plugin and Claude Desktop bundle both switch to generated, curated configuration screens with an in-session repair path, replacing shell-profile edits. Three things change on upgrade without a config change on your part: the write tools ship enabled by default, fetch now follows redirects, and from markdown_vault_mcp import ... no longer works. Import from submodules instead.

This is the first stable release since 3.1. It carries five epics (OKF support, the summarize tool's evolution into a background-job platform, search ranking configuration, the plugin and Desktop install experience, and the release process itself moving to branch-aware, knope-driven release PRs), plus a security-hardening pass on the fetch tool and a long tail of correctness fixes to folder-scoped search, link extraction, and the vector sidecar.

Upgrade notes

Five things change behavior on upgrade. Two need no code change to notice; read them first.

The write tools ship enabled by default

MARKDOWN_VAULT_MCP_READ_ONLY now defaults to false, where it defaulted to true through 3.1 (#1113, #1119). An operator who set nothing and relied on the old default gets a vault an LLM client can now write to, edit, and delete from. Set MARKDOWN_VAULT_MCP_READ_ONLY=true explicitly to keep a search-only instance.

The flip exposes seven tools that were previously hidden: write, edit, append, delete, rename, move_folder, and fetch. Four more write-tagged tools stay behind their own separate gates regardless of this setting: git_sync (needs managed git mode), create_upload_link (needs an HTTP transport), and the okf_* write tools (need OKF semantics active, and okf_verify also needs OKF_WRITE=true). The Python library default is unchanged: Vault(..., read_only=True) remains the fail-safe default for code that omits the argument.

fetch now follows redirects

Through 3.1, fetch refused any URL that redirected. It now follows the chain and returns the resolved end-of-chain URL as a new final_url field (#1116, #1118). This was a deliberate call, not an oversight: the SSRF guard now checks scheme, address, and IP pin again on every hop instead of once before the first request, so this per-hop check is a stronger guarantee than the old blanket refusal, and ordinary indirection (http to https, shortened links, CDN hand-offs) now resolves instead of failing. A caller that used the redirect refusal as a way to reject indirection now gets content instead; there is no opt-out.

summarize, reindex, and build_embeddings changed their polling shape

Both reindex/build_embeddings and summarize moved onto a shared dual-mode background-job mechanism this release (#1033, #1037, #1034). A call that finishes within MARKDOWN_VAULT_MCP_JOBS_SOFT_DEADLINE_S (default 25 seconds) now returns its real result inline instead of a {"status": "queued"} placeholder. A call that runs longer is promoted to a background job and polled with the generic get_job_result tool, which replaces the summarize-specific get_summary tool entirely. A caller built against the old queued/get_summary shape needs to move to the new one.

The Anthropic-specific summarize backend is gone

summarize no longer talks to the Anthropic API directly. It speaks a generic OpenAI-compatible chat-completions API instead: the same client code now serves OpenAI, Ollama, Anthropic's own OpenAI-compatible endpoint, vLLM, LM Studio, OpenRouter, and LiteLLM without per-provider branching (#915, #917). A bare ANTHROPIC_API_KEY or MARKDOWN_VAULT_MCP_SUMMARIZE_ANTHROPIC_MODEL is no longer read, and MARKDOWN_VAULT_MCP_SUMMARIZE_PROVIDER=anthropic now raises a configuration error naming the migration path: point MARKDOWN_VAULT_MCP_SUMMARIZE_OPENAI_BASE_URL at Anthropic's https://api.anthropic.com/v1/ endpoint instead.

Importing from the package root no longer works

from markdown_vault_mcp import Vault (and every other name previously re-exported at the package root, including ProjectConfig, the exception hierarchy, the types dataclasses, and GitWriteStrategy) now raises ImportError. The package root shrank to a docstring and __version__, matching the shared template's minimal-skeleton convention (#903, #912). Import from the owning submodule instead: from markdown_vault_mcp.vault import Vault, from markdown_vault_mcp.config import ProjectConfig, and so on. See the source layout for which submodule owns which name.

This is a breaking change to the public library interface under this project's own versioning policy, and issue #903 said so explicitly before the work started ("This is a breaking change to the library's public import surface"). The merged commit's title did not carry a breaking-change marker. This page's classification follows the issue, not the commit: MCP tool callers are unaffected (nothing here touches a registered tool), but a Python program importing this package from its root breaks and needs the submodule-import migration above.

Highlights

OKF (Open Knowledge Format) support

Google's Open Knowledge Format (spec v0.2, announced June 2026) standardizes exactly the artifact this server already indexes: a directory of markdown files with YAML frontmatter, curated as context for AI agents. The server was already most of the way there by construction (frontmatter-aware indexing, permissive parsing, broken-link tolerance, a wikilink-and-markdown-link graph, git versioning), but had no OKF-specific semantics for the type/status/stale_after field family or the index.md/log.md reserved-file conventions (#959). This release adds them, end to end.

Two independent channels control it, deliberately kept apart: a vault declares okf_version in its root index.md and that declaration only ever buys read semantics and advisory conventions; an operator's environment config is the only thing that can authorize write behavior. The design rationale, quoted directly: the vault is writable by the agent itself through the write tool, so "data that could enable byte-changing or write-refusing server behavior would be a self-modifying-config loop." Vault content may only ever advise, never authorize.

  • Detection and read-side annotation. With MARKDOWN_VAULT_MCP_OKF_MODE at its default auto, declaring okf_version in the vault's root index.md switches on okf annotations (type, derived status, staleness, trust tier) in search, read, and get_context results, and an okf section in stats. Set off to disable OKF semantics entirely, or on to force them without a declaration (#960, #968).
  • Filtering and graph typing. search, list_documents, and get_similar gain status, stale, and trust_tier filter dimensions, and graph views color nodes by OKF type (#961, #970).
  • okf_validate, a disk-based conformance audit that reports conformance as a degree rather than a verdict: a count of conformant notes out of the total, plus per-rule violation counts across three severities (#962, #971).
  • Migration and bundle export. okf_convert_links rewrites [[wikilinks]] to OKF's recommended bundle-root-absolute markdown links without breaking the link graph; okf_generate_index and okf_seed_log populate a folder's index.md and log.md (the latter from git history, scoped to the folder's own subtree); bundle export is a create_download_link call with ref="okf-bundle" (or "okf-bundle:<folder>" for a subtree), not a separate tool (#963, #973, #982).
  • An enforced write layer, off by default (MARKDOWN_VAULT_MCP_OKF_WRITE=false). Turned on for an active bundle, every write stamps generated: {by, at} provenance and clears any prior verified attestation when content changes, and a folder's log.md/ index.md stay current automatically (#964, #984, #987). The okf_verify tool that records a human review was hardened against self-attestation after review found that a model holding a human's auth token could stamp verified: {by: "human:..."} without a human ever reviewing anything. MARKDOWN_VAULT_MCP_OKF_VERIFY now governs it: the default elicit asks the human to confirm through an MCP elicitation and writes the attestation only on an affirmative reply, failing closed on an unsupported client or a decline; trust-auth restores the original token-attribution behavior for interfaces driven directly by a human; off hides the tool (#990, #1004).
  • Ranking downweights, active only on a declared bundle: status: deprecated scores at 0.5x, a stale note (past stale_after) at 0.75x, and the reserved index.md/log.md files at 0.5x, applied across keyword, semantic, and hybrid search alike (#965, #988). Trust tier deliberately does not boost ranking. It stays an annotation so the agent applies that judgment visibly, rather than the ranker reshuffling results silently.

The OKF guide covers the full migration path (audit, declare, enrich, convert) and interop notes for PARA and Zettelkasten vaults, alongside an example pack of declaration templates and prompts.

Summarization becomes a platform

summarize shipped two releases ago as a single LLM call over a note, a set of notes, or a subtree. This release turns it into something that scales to a folder of any size and degrades gracefully when nothing is configured.

The character budget that previously capped how much of a folder could be covered in one call now caps a single model request instead: notes are packed into batches, each batch is summarized independently with bounded parallelism, and, in the default synthesis mode, the partial summaries are combined into one (#922, #924). A 262-note folder that previously silently covered 11 notes and reported only truncated: true now covers all of them, or reports precisely how many were included and omitted. A per-call max_notes parameter and a hint field that appears whenever notes were omitted let a caller plan a subfolder split itself instead of guessing (#925, #926).

Latency is now bounded rather than open-ended: each backend call is capped by MARKDOWN_VAULT_MCP_SUMMARIZE_TIMEOUT (default 120 seconds), and the tool call itself runs inline up to MARKDOWN_VAULT_MCP_JOBS_SOFT_DEADLINE_S (default 25 seconds) before promoting to a background job (the dual-mode mechanism described in Upgrade notes above) (#937, #1034). Along the way, a reasoning model (the new default, gpt-5-mini) exhausting its entire token budget on internal reasoning before producing visible output is fixed by raising the default MARKDOWN_VAULT_MCP_SUMMARIZE_MAX_TOKENS from 2048 to 8192 and requesting low reasoning effort (#919, #920), and output no longer ends with chat-assistant offers like "if you want I can draw a diagram" (#921, #923).

For an install with no summarization backend configured at all, or a vault too privacy-sensitive to send content to an external API, a new summarize-subtree MCP prompt delegates the same map-reduce recipe to the client's own model instead: a planner subagent fans out parallel mapper subagents that each read and summarize their own batch, keeping note bodies out of the primary conversation's context window (#1035, #1038). When a backend is configured, the prompt recommends the single-call summarize tool first and offers itself as the fallback for scopes that don't fit in one call.

Precision search: frontmatter fields, ranking weights, consistent scoping

A curated vault keeps its highest-signal text in frontmatter (a display name, a hand-written summary) that search previously never saw, because it only ever indexed the note body. Contributed by @mikebronner against a concrete case (a 1,400-document memory vault where machine-written session summaries crowded out curated notes in every search), five new settings fix this without touching the corpus (#870, #867): MARKDOWN_VAULT_MCP_TITLE_FIELD (any frontmatter key can serve as the document title), SEARCHABLE_FIELDS (chosen frontmatter fields become keyword-searchable and enrich embeddings), EMBED_CONTEXT (prepend title/heading context to embedded chunk text), FOLDER_WEIGHTS (a per-folder rank multiplier, longest-prefix-wins), and FTS_WEIGHTS (per-column BM25 weights across path, title, folder, heading, content, and summary). All five default to an exact no-op: nothing changes for a vault that sets none of them.

Two long-standing search gaps close alongside it. Keyword and hybrid search previously returned nothing for a hyphenated query term (File-back Ingest Lint) because FTS5 parses an unquoted hyphen as an operator, silently swallowed to an empty result, reported by @vomos-ua (#866, #884). And folder- and filter-scoped search was inconsistently honored across search surfaces: an empty-string folder was silently treated as no restriction on every vector-backed path, a trailing slash returned nothing on every FTS-backed one, and a narrow folder's matches could fall outside semantic search's candidate pool before that pool was widened; all fixed together this release (#1103, #1106, #1108).

Changing MARKDOWN_VAULT_MCP_INDEXED_FIELDS now triggers the one-time rebuild it always should have, and SEARCHABLE_FIELDS inherits INDEXED_FIELDS when left unset, so one field list expresses one intent instead of two (#927, #928).

A guided path onto Claude Code and Claude Desktop

Installing the Claude Code plugin previously meant exporting environment variables to a shell profile and restarting Claude Code (for even the one required setting, the vault directory), with no in-product path to a fix if you skipped a step or the vault later moved. The Claude Desktop bundle's configuration screen had drifted from reality in the other direction: 26 hand-maintained fields including HTTP-auth settings that do nothing on a stdio-only bundle, while newer settings were missing entirely (#1043).

Both channels now show a small, generated configuration screen of 14 curated fields (vault directory, read-only toggle, exclude patterns, embedding provider and credentials, git sync URL and token, server name, log level), sharing one field map as their single source of truth, so the two channels cannot drift apart again (#1040, #1041). The Claude Code plugin's values now live in userConfig and persist across plugin updates instead of a shell-exported env var. When something is still wrong (no vault configured, or a configured vault directory that no longer exists), a SessionStart hook now speaks up only on failure and points the model at a vault-setup skill that discovers candidate vaults, validates one, and writes the working configuration (#1042). A vault-summarize skill and vault-mapper subagent give the plugin its own client-side summarization path, built on the same recipe as the summarize-subtree prompt above (#1036).

Release-to-marketplace publishing is also now direct rather than a bump pull request that could sit without being merged for months. The epic that drove this work found fifteen such PRs open across the marketplace catalog at once, including this project's own. This release is also the first to close a narrower bug in the same area: because the plugin's version pin was rewritten on every release including release candidates, main could briefly point the plugin at an rc version that was never published to PyPI; the 4.0.0 stable cut is the point where that pin resolves to a real, installable version again (#1053).

See the Claude Code plugin and Claude Desktop guides for the current setup flow.

Other changes

  • A new append tool writes to the end of a note without a prior read, contributed by @zbingos building an Obsidian workflow: every append previously had to go through edit, which forced reading the whole note first purely to construct the diff (#980, #1025).
  • MARKDOWN_VAULT_MCP_EMBED_TIMEOUT_S (default 30 seconds) and MARKDOWN_VAULT_MCP_EMBEDDING_BATCH_SIZE (default 4) are now configurable rather than hard-coded, after @andreas8horvath reported 61 embed timeouts in one afternoon running a CPU-only Ollama model where a single large batch routinely took 20 to 44 seconds (#954, #1000).
  • Two classes of false-positive broken links are fixed: a same-note heading link ([[#Section]]) resolving to a literal, always-broken .md target, and markdown footnote definitions being mis-extracted as vault links entirely (#1104, #1107).
  • The vector sidecar is more durable: embedding convergence no longer reclaims vectors for sources it merely failed to see on an incomplete directory walk, up to and including wiping the whole sidecar in the worst case (#1130); a note with malformed frontmatter or an empty body no longer aborts an entire embedding batch (#994, #1112); and a single embedding-provider timeout or vector-dimension mismatch no longer fails a whole reindex or convergence pass (#930, #935).
  • The index now tracks its own derivation logic and self-heals when it changes: a future fix to link, tag, or chunk extraction now triggers an automatic one-time rebuild instead of leaving stale rows behind indefinitely, and the reindex tool and CLI gained a manual force flag as an escape hatch that did not exist before (#1124, #1125).

One-time upload and download links now survive a server restart: the token store moved from an in-memory dictionary onto fastmcp-pvl-core's shared, KV-backed transfer subsystem, retiring roughly 800 lines of local machinery in the process (#979, #981). A successful transfer no longer burns its token outright either: its remaining lifetime shrinks to MARKDOWN_VAULT_MCP_TRANSFER_GRACE_TTL_S (default 60 seconds) instead, so a served-but-stalled transfer can still retry. The create_download_link/create_upload_link call contract itself is unchanged. See the transfer links guide for the full parameter reference.

Security

  • The fetch tool's SSRF guard closed two gaps: carrier-grade NAT address space (100.64.0.0/10) was not on its block list, and the outbound HTTP client trusted ambient proxy environment variables, so an HTTP_PROXY/HTTPS_PROXY set in the server's environment could route a fetch around the entire address-validation chain (#1029).
  • A transitive dependency, mcp, was bumped from 1.28.0 to 1.28.1 to resolve CVE-2026-59950, flagged by the project's dependency-audit gate (#934).

Thanks

This release includes four reports from outside the repository:

  • @mikebronner contributed configurable title fields, searchable frontmatter, and ranking weights directly (#867).
  • @vomos-ua reported the hyphenated-term search gap (#866).
  • @zbingos requested the append tool while building an Obsidian workflow (#980).
  • @andreas8horvath reported the embedding-timeout cascade under a CPU-only Ollama setup, with measured failure counts (#954).

Patch releases

No patch releases yet.

All changes

See CHANGELOG.md for the full commit-level list, or the v3.1.0 to 4.0.0 comparison.