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_MODEat its defaultauto, declaringokf_versionin the vault's rootindex.mdswitches onokfannotations (type, derivedstatus, staleness, trust tier) insearch,read, andget_contextresults, and anokfsection instats. Setoffto disable OKF semantics entirely, oronto force them without a declaration (#960, #968). - Filtering and graph typing.
search,list_documents, andget_similargainstatus,stale, andtrust_tierfilter 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_linksrewrites[[wikilinks]]to OKF's recommended bundle-root-absolute markdown links without breaking the link graph;okf_generate_indexandokf_seed_logpopulate a folder'sindex.mdandlog.md(the latter from git history, scoped to the folder's own subtree); bundle export is acreate_download_linkcall withref="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 stampsgenerated: {by, at}provenance and clears any priorverifiedattestation when content changes, and a folder'slog.md/index.mdstay current automatically (#964, #984, #987). Theokf_verifytool that records a human review was hardened against self-attestation after review found that a model holding a human's auth token could stampverified: {by: "human:..."}without a human ever reviewing anything.MARKDOWN_VAULT_MCP_OKF_VERIFYnow governs it: the defaultelicitasks 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-authrestores the original token-attribution behavior for interfaces driven directly by a human;offhides the tool (#990, #1004). - Ranking downweights, active only on a declared bundle:
status: deprecatedscores at 0.5x, a stale note (paststale_after) at 0.75x, and the reservedindex.md/log.mdfiles 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¶
Search, indexing, and links¶
- A new
appendtool writes to the end of a note without a prior read, contributed by @zbingos building an Obsidian workflow: every append previously had to go throughedit, which forced reading the whole note first purely to construct the diff (#980, #1025). MARKDOWN_VAULT_MCP_EMBED_TIMEOUT_S(default 30 seconds) andMARKDOWN_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.mdtarget, 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
reindextool and CLI gained a manualforceflag as an escape hatch that did not exist before (#1124, #1125).
Transfer links¶
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
fetchtool'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 anHTTP_PROXY/HTTPS_PROXYset 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
appendtool 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.