MCP Tools¶
markdown-vault-mcp exposes MCP tools across several categories. Write tools are only available when MARKDOWN_VAULT_MCP_READ_ONLY=false.
Index freshness on read tools (wait_for_pending_writes + _meta.index_stale)
Every read tool that queries the FTS index (search, list_documents, list_folders, list_tags, stats, get_recent, get_backlinks, get_outlinks, get_broken_links, get_similar, get_context, get_orphan_notes, get_most_linked, and get_connection_path) accepts an optional wait_for_pending_writes (bool, default false) parameter and reports index freshness out-of-band in the MCP response's _meta.index_stale field rather than wrapping the payload in a {stale, data} envelope. The data payload is a bare list/dict, identical whether the index is fresh or stale; clients that do not care about drift ignore _meta entirely. Clients that need a fresh-read guarantee either inspect result._meta.index_stale, or pass wait_for_pending_writes=true to block until the writer drains (bounded by MARKDOWN_VAULT_MCP_DRAIN_TIMEOUT_S, default 60 s; on timeout it answers from the current index rather than raising). index_stale is true when the IndexWriter had pending or in-flight work. The relevant conditions are: the optional wait_for_pending_writes timed out; a write completed inside the read window; the writer was non-idle at response time. The same _meta.index_stale field rides on the index-querying MCP resources (config://, stats://, folders://, tags://, recent://, toc://, similar://), readable via the resource read's _meta (resources carry no wait_for_pending_writes parameter; they signal only).
Quick Reference¶
| Tool | Category | Description |
|---|---|---|
search |
Read | Hybrid full-text + semantic search with optional frontmatter filters |
read |
Read | Read a document or attachment by relative path |
list_documents |
Read | List indexed documents and optionally attachments |
list_folders |
Read | List all folder paths in the vault |
list_tags |
Read | List all unique frontmatter tag values |
stats |
Read | Get vault statistics and capabilities |
embeddings_status |
Read | Check embedding provider and vector index status |
get_index_status |
Read | Check background FTS build state (queryable / building / failed) |
get_backlinks |
Read | Find all documents that link to a given document |
get_outlinks |
Read | Find all links from a document, with existence check |
get_broken_links |
Read | Find all links pointing to non-existent documents |
get_similar |
Read | Find semantically similar notes by document path |
get_recent |
Read | Get the most recently modified notes |
get_context |
Read | Get a consolidated context dossier for a note |
get_orphan_notes |
Read | Find notes with no inbound or outbound links |
get_most_linked |
Read | Find the most-linked-to notes ranked by backlink count |
get_connection_path |
Read | Find the shortest path between two notes via link graph |
get_history |
Read (git) | List commits that touched a note, attachment, or the whole vault |
get_diff |
Read (git) | Return a diff of a note or attachment between two points in history |
reindex |
Admin | Force a full reindex of the vault |
build_embeddings |
Admin | Build or rebuild vector embeddings |
write |
Write | Create or overwrite a document or attachment |
edit |
Write | Replace a unique text span in a document |
delete |
Write | Delete a document or attachment |
rename |
Write | Rename/move a document or attachment |
fetch |
Write | Download from URL and save to vault |
git_sync |
Write (git) | Force an immediate git pull / push / both, bypassing the periodic loops |
create_download_link |
Transfer | Mint a one-time capability URL to download a vault file (HTTP/SSE only) |
create_upload_link |
Transfer | Mint a one-time capability URL to upload bytes to a fixed vault path (HTTP/SSE only) |
browse_vault |
Apps | Open the vault explorer SPA |
show_context |
Apps | Open the Context Card for a note |
Search & Discovery¶
search¶
Find documents matching a query using full-text or semantic search.
Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
query |
string | required | Natural language or keyword query string |
limit |
int | 10 |
Maximum results to return |
mode |
string | "keyword" |
"keyword" (FTS5/BM25), "semantic" (vector similarity), or "hybrid" (reciprocal rank fusion) |
folder |
string | null |
Restrict to documents under this folder path |
filters |
object | null |
Filter by indexed frontmatter field values (such as {"tags": "pacing"}) |
chunks_per_file |
int | server default (2) |
Maximum number of matching sections returned per file. Overrides MARKDOWN_VAULT_MCP_CHUNKS_PER_FILE for this call. 0 is rejected. |
snippet_words |
int | server default (200) |
Approximate word budget for each section's content field. 0 returns the full chunk. Overrides MARKDOWN_VAULT_MCP_SNIPPET_WORDS for this call. |
Returns: List of grouped result dicts ranked by relevance, one entry per file with up to chunks_per_file best-matching sections. Each entry contains: path, title, folder, score (max section score), search_type, frontmatter, and sections (a list of {heading, content, score} dicts sorted by score then document order).
Grouped result shape
Each file appears at most once in results, with up to chunks_per_file sections nested under sections. The top-level score is the maximum of the section scores (MaxP aggregation). Iterate sections to drill into individual matches.
Snippet content and full-chunk recovery
By default, each section's content is a snippet of approximately 200 words centered on the query terms (not the full chunk). Pass snippet_words=0 to receive the complete chunk. To read the full section after receiving a search result, call read(path=result["path"], section=result["sections"][0]["heading"]); this returns the entire chunk from the index without re-reading the whole document.
Choosing a search mode
- Use
mode="hybrid"when semantic search is available, combining keyword precision with semantic understanding - Use
mode="keyword"for exact term matches - Use
mode="semantic"for meaning-based similarity - Check
statsto see ifsemantic_search_availableis true
Example usage:
{
"query": "character development techniques",
"mode": "hybrid",
"limit": 5,
"filters": {"tags": "craft"}
}
read¶
Read the full content of a document or attachment by path. When combined with search, the optional section parameter lets you retrieve the full content of a specific chunk without loading the entire document.
Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
path |
string | required | Relative path to the document or attachment (such as "Journal/note.md" or "assets/diagram.pdf") |
section |
string | null |
Optional heading to select a single section chunk. Pass the heading field from a search result to retrieve the full chunk content. Matching collapses internal whitespace on both sides: "1.3. Reducing..." (two spaces) matches a stored "1.3. Reducing..." (one space) and vice versa. On miss, the error lists the document's actual stored headings so callers can recover. Raises an error if the heading is not found or is empty. |
Recovering full chunks after search
When search returns a snippet result, pass result["heading"] as the section parameter to recover the complete chunk: read(path=result["path"], section=result["heading"]). If the document has no sub-headings (preamble content), omit section to read the whole document.
Heading matching tolerates whitespace differences
The section lookup compares heading strings after collapsing all whitespace runs to single spaces (and stripping leading/trailing whitespace). This handles the common case where an LLM caller infers a heading from a rendered TOC that normalises whitespace differently from the source markdown. Markdown emphasis (**bold**, _italic_) and case still matter; pass the heading as it would appear in the document source.
Context cost: every byte returned counts against the LLM's context
budget. Reads above MARKDOWN_VAULT_MCP_MAX_NOTE_READ_BYTES (default
256 KB for .md) or MARKDOWN_VAULT_MCP_MAX_ATTACHMENT_SIZE_MB (default
1 MB for binaries) raise an error; use section=result["heading"] for
partial markdown reads (see the tip above).
Returns:
{
"path": "Journal/note.md",
"title": "My Note",
"folder": "Journal",
"content": "The markdown body...",
"frontmatter": {"title": "My Note", "tags": ["journal"]},
"modified_at": 1741564800.0
}
{
"path": "assets/diagram.pdf",
"mime_type": "application/pdf",
"size_bytes": 12345,
"content_base64": "<base64 string>",
"modified_at": 1741564800.0
}
list_documents¶
List documents (and optionally attachments) in the vault.
Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
folder |
string | null |
Return only documents in this folder |
pattern |
string | null |
Unix glob matched against relative paths (such as "Journal/*.md") |
include_attachments |
bool | false |
When true, also returns non-.md files that match the configured allowlist |
Returns: List of info dicts. Every entry has a kind field ("note" or "attachment"). Body content is not included; call read for full text.
list_folders¶
List all folder paths that contain documents. Use this to discover valid folder names for filtering search or list_documents. The root folder (top-level documents) is represented as the empty string "".
Returns: Sorted list of folder paths, such as ["", "Journal", "Projects"].
list_tags¶
List all distinct values for a frontmatter field across the vault.
Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
field |
string | "tags" |
Frontmatter field name to enumerate. Must match a field in indexed_frontmatter_fields (check stats) |
Returns: Sorted list of distinct string values, such as ["craft", "pacing", "worldbuilding"].
stats¶
Get an overview of the vault's size, capabilities, and configuration. Call this at the start of a session to understand what the vault contains and what search modes are available.
Returns:
{
"document_count": 42,
"chunk_count": 156,
"folder_count": 5,
"semantic_search_available": true,
"indexed_frontmatter_fields": ["tags", "cluster"],
"attachment_extensions": ["pdf", "png", "jpg"]
}
embeddings_status¶
Check the embedding provider configuration and vector index status. Use this to diagnose why semantic search is unavailable.
Returns:
{
"available": true,
"provider": "OllamaProvider",
"chunk_count": 156,
"path": "/data/state/embeddings/embeddings"
}
get_index_status¶
Returns background-build state of the FTS index. Use this when
initialize returned but bucket-3/4 calls block longer than expected
or surface IndexUnavailableError (with reason of "never_built",
"build_failed", "timeout", "broken", or "busy"). The status field
distinguishes "still building" from "build failed," and the error
field carries the diagnostic message from the last failed background
build attempt.
Returns:
- status: "queryable", "building", or "failed".
- documents_indexed: count of documents committed to the FTS index
right now (rises during "building"). 0 both for an empty index
and when the count could not be read; check documents_indexed_error
to tell them apart.
- documents_indexed_error: null on a normal read; the SQLite error
message when the document count could not be read (such as a locked or
closed database), in which case documents_indexed is 0.
- error: null unless the background build raised; otherwise the
exception message.
Tags: read-only.
Index Management¶
Cold-start blocking
Calls to reindex and build_embeddings during a cold-start background FTS build block via the tool-layer needs_queryable decorator. If the build takes longer than MARKDOWN_VAULT_MCP_BUILD_TIMEOUT_S (default 60 s), the tool returns IndexUnavailableError(reason="timeout"). The same exception fires with reason="build_failed" if a scheduled background build ran and failed; read get_index_status's error field for the captured diagnostic. The decorator also remaps a SQLite OperationalError from the handler call to IndexUnavailableError(reason="broken") (corruption / I/O failure / unknown codes) or reason="busy" (SQLITE_BUSY/LOCKED, lock contention); inspect the exception's __cause__ for the underlying SQLite error. Poll get_index_status to observe build state without blocking.
reindex¶
Incrementally update the full-text search index to reflect file changes made outside this server. Only changed files are processed; unchanged documents are skipped, and files deliberately excluded from the index (missing required frontmatter, exclude-pattern matches, unparseable content) are remembered across scans so they are not re-parsed or re-reported until their content changes (#665).
If semantic search is configured, the queued reindex job re-embeds the changed documents on the writer thread. Poll get_index_status and watch the dirty_embeddings counter to observe completion.
Boot reconciliation
The server lifespan automatically queues one incremental reindex at every startup (#665), so files added, modified, or deleted while no server was running are reconciled without a manual reindex call. Reads served before that job completes report index_stale: true in _meta.
Returns: {"status": "queued"}. The reindex runs asynchronously on the single-owner :class:IndexWriter thread (#559); poll get_server_info or get_index_status for completion. get_index_status exposes queue_depth, in_flight, dirty_paths, and dirty_embeddings so you can observe progress without blocking.
build_embeddings¶
Build vector embeddings to enable semantic and hybrid search. This can be slow for large vaults.
Without force, an existing vector index is converged to the FTS chunk set (#665). The operation embeds missing documents, re-embeds those whose indexed content changed, and drops vectors for deleted or excluded documents. Work scales with the size of the drift, not the size of the vault; an already-converged index does no embedding work.
Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
force |
bool | false |
When true, discards existing embeddings and rebuilds from scratch. Use only if the embedding model has changed |
Returns: {"status": "queued"}. The build runs asynchronously on the single-owner :class:IndexWriter thread (#559); poll get_server_info or get_index_status for completion.
When to use
Normally never: the server queues a build_embeddings job at every startup, which converges the vector index to whatever the boot reconciliation reindex found (#665). Manual calls are needed in three cases: embedding a vault for the first time without restarting; retrying after a provider outage; or rebuilding from scratch after an embedding-model change (pass force=true).
Write Operations¶
Write tools require MARKDOWN_VAULT_MCP_READ_ONLY=false
These tools are hidden when the server is in read-only mode (the default).
write¶
Create or overwrite a document or attachment.
Parameters:
| Parameter | Type | Description |
|---|---|---|
path |
string | Relative path. Extension determines handling (.md = note, else attachment) |
content |
string | Full markdown body for .md files (excluding frontmatter). Ignored for attachments |
frontmatter |
object | Optional YAML frontmatter dict for .md files. Ignored for attachments |
content_base64 |
string | Base64-encoded binary content for attachment files. Required when path is not .md |
Context cost: the content parameter (text) is bounded only by the
LLM's own output budget. The content_base64 parameter (binary) inflates
by ~33%.
Returns: {"path": "Journal/note.md", "created": true}
Warning
write replaces the entire file. Use edit for targeted changes to existing documents.
edit¶
Make a targeted text replacement in an existing document. Supports three modes:
- Exact match (
old_textonly): must appear exactly once in the document. - Line-range (
line_start+line_end, noold_text): replaces the specified lines. Passif_matchfor safety. - Scoped match (
old_text+line_start/line_end): searches forold_textwithin the specified line range only.
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
path |
string | Yes | Relative path to the document |
old_text |
string | Conditional | Text to replace. Required unless using line-range mode |
new_text |
string | Yes | Replacement text |
if_match |
string | No | Etag from read for optimistic concurrency |
line_start |
integer | Conditional | First line to replace (1-based, inclusive). Required with line_end |
line_end |
integer | Conditional | Last line to replace (1-based, inclusive). Required with line_start |
Returns: {"path": "Journal/note.md", "replacements": 1, "match_type": "exact"}
match_type is "exact" when the text matched byte-for-byte, or "normalized" when it matched after Unicode/whitespace normalization.
Usage pattern
Always call read first to get the exact current text and line numbers. For small edits, use old_text (exact match). For large block replacements, use line_start/line_end with the line numbers shown by read. Frontmatter can be edited; old_text may span the YAML block.
Normalized matching
When exact match fails, the tool automatically tries a normalized comparison. Normalization covers Unicode NFC, whitespace collapsing, and smart quote conversion (en-dash/em-dash to hyphen). If a unique match is found, it proceeds and returns match_type: "normalized".
Diagnostic errors
When no match is found, the error message reports the closest matching line number and the character position of the first difference, along with short snippets showing what was expected vs. what was found. This helps identify the exact mismatch.
delete¶
Permanently delete a document or attachment. For .md documents, also removes from all search indices.
Parameters:
| Parameter | Type | Description |
|---|---|---|
path |
string | Relative path to the document or attachment to delete |
Returns: {"path": "Journal/old-note.md"}
Danger
This is irreversible unless git history exists. Confirm the path with the user before calling.
rename¶
Rename a document or attachment, or move it to a different folder. Parent directories for the new path are created automatically.
Parameters:
| Parameter | Type | Description |
|---|---|---|
old_path |
string | Current relative path |
new_path |
string | Target relative path. Fails if new_path already exists |
Returns: {"old_path": "drafts/idea.md", "new_path": "projects/idea.md"}
fetch¶
Download a file from a URL and save it to the vault as a note or attachment. Designed for MCP-to-MCP file transfer when content is too large for the LLM context window.
Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
url |
string | required | Source URL to download. Only http/https schemes allowed; the host is resolved and private or internal addresses are blocked; the validated IP is pinned for the connection; redirects are not followed (SSRF protection) |
path |
string | required | Destination path in vault. Extension determines handling: .md for notes, anything else for attachments |
frontmatter |
object | null |
Optional YAML frontmatter dict for .md files. Ignored for attachments |
if_match |
string | null |
Optional etag from a previous read call for optimistic concurrency |
timeout_s |
float | 30.0 |
Download timeout in seconds |
Context cost: zero. The file is downloaded server-side. Reference
the saved file by path for downstream tools rather than read()-ing it
back into context.
Returns: {"path": "notes/report.md", "created": true, "content_length": 4096, "content_type": "text/markdown"}
Dependency
Requires httpx. Install with pip install 'markdown-vault-mcp[all]'.
git_sync¶
Force an immediate git pull / git push / both, bypassing the periodic
pull interval and write-idle push delay. Returns a structured payload
with the local HEAD SHA, branch, and per-leg results so an LLM agent can
confirm "your changes are now on the remote" or recover from a divergent
history before continuing the conversation.
Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
direction |
"pull" | "push" | "both" |
"both" |
Which legs to run. "both" runs pull first, then push; if pull fails (pull.applied=false) the push leg is skipped and push stays null so a callable can inspect pull.reason before retrying. |
dry_run |
bool | false |
When true, the pull leg runs git fetch and reports what would happen (would_apply: bool, projected to_sha) without moving HEAD. The push leg has no safe local probe for "would the remote accept this," so a dry-run push is a no-op that returns applied=false with reason="dry_run_unsupported". |
Returns: Dict with the following fields:
direction(str): the requested direction, echoed back.head_sha(str): local HEAD SHA after the operation. Differs from the pre-call HEAD when the pull leg advanced the branch.branch(str): current branch name (or"HEAD"on detached HEAD).pull(dict | null): payload from the pull leg, ornullwhendirection="push". Fields:applied,fast_forward,commits_pulled,from_sha,to_sha; optionalreason,conflict_files;would_apply(only indry_runmode).commits_pulledis reliable on the fast-forward path. Onreason="rebased"andreason="conflicts_resolved_with_siblings"it is0even when HEAD advanced (the rebase replays local commits on top of the upstream rather than fast-forwarding); inspectfrom_sha != to_shato detect the actual change.push(dict | null): payload from the push leg, ornullwhendirection="pull"or when the pull leg failed indirection="both". Fields:applied,commits_pushed,remote_sha_before,remote_sha_after; optionalreason,hint.dry_run(bool): present only whendry_run=truewas passed.
Examples:
Successful both-direction sync (clean fast-forward + clean push):
{
"direction": "both",
"head_sha": "abc1234",
"branch": "main",
"pull": {
"applied": true,
"fast_forward": true,
"commits_pulled": 3,
"from_sha": "9999999",
"to_sha": "abc1234"
},
"push": {
"applied": true,
"commits_pushed": 5,
"remote_sha_before": "8888888",
"remote_sha_after": "abc1234"
}
}
Pull with conflict (Syncthing-style sibling resolution per #232):
{
"direction": "pull",
"head_sha": "abc1234",
"branch": "main",
"pull": {
"applied": true,
"fast_forward": false,
"commits_pulled": 0,
"from_sha": "9999999",
"to_sha": "abc1234",
"reason": "conflicts_resolved_with_siblings",
"conflict_files": ["Notes/2026-05-09.conflict-mcp-20260511-114203.md"]
},
"push": null
}
The pull succeeded (applied=true): HEAD now points at the remote tip
and the local edits that conflicted with the remote were preserved as
.conflict-mcp-<timestamp>.md siblings on the same path. The remote
version wins on the canonical path; the LLM should read the listed
each sibling and propose how to merge the local content back in.
commits_pulled is 0 on this path because the rebase replays local
commits on top of the remote (the remote commits are reconciled, not
"pulled forward" in the linear-history sense).
Push rejected as non-fast-forward:
{
"direction": "push",
"head_sha": "abc1234",
"branch": "main",
"push": {
"applied": false,
"commits_pushed": 0,
"remote_sha_before": "9999999",
"remote_sha_after": "9999999",
"reason": "non_fast_forward",
"hint": "Remote has commits the local clone has not seen. Run git_sync(direction='pull') to reconcile (fast-forward when possible, Syncthing-style siblings on real conflict), then retry git_sync(direction='push')."
}
}
pull.reason values (set on every non-fast-forward outcome and on
failures; null for clean fast-forwards and dry-runs):
| Reason | Meaning | applied |
|---|---|---|
"fetch_failed" |
git fetch origin exited non-zero (network / auth / proxy). HEAD did not move. |
false |
"no_remote" |
Neither @{upstream} nor origin/HEAD could be resolved on the local clone. |
false |
"rebased" |
Local and remote diverged but git rebase @{upstream} replayed local commits cleanly. conflict_files empty. |
true |
"conflicts_resolved_with_siblings" |
Rebase hit real conflicts; resolved by accepting upstream and writing local versions as .conflict-mcp-* siblings (#232). conflict_files populated. |
true |
"conflict_resolution_failed" |
The conflict-resolution loop could not produce a recoverable working tree; rebase was aborted. HEAD did not move. | false |
"non_fast_forward_with_conflicts" |
Rare catastrophic fallback when even the conflict-resolution path could not stabilise the working tree. HEAD did not move. | false |
push.reason values (null on success including the
already-up-to-date no-op):
| Reason | Meaning | applied |
|---|---|---|
"dry_run_unsupported" |
Caller passed dry_run=true. Git has no safe local probe for "would the remote accept this," so the push leg is a deliberate no-op. |
false |
"no_remote" |
Upstream tracking branch could not be resolved (no @{upstream} and no origin/HEAD). Push not attempted. |
false |
"non_fast_forward" |
Remote rejected the push because the local branch is not a strict descendant of the remote tip. hint points at git_sync(direction='pull') to reconcile first. |
false |
"push_failed" |
git push origin exited non-zero for any other reason (network, auth, server-side hook). hint carries the truncated stderr. |
false |
Context cost: small (structured dict only, no file bytes).
Tag: {write, git-managed}. Hidden when
MARKDOWN_VAULT_MCP_READ_ONLY=true or when the deployment is not in
managed git mode (MARKDOWN_VAULT_MCP_GIT_REPO_URL not set).
Errors:
ValueError: raised at call time when the underlying strategy is not a managedGitWriteStrategy(that is,MARKDOWN_VAULT_MCP_GIT_REPO_URLis unset). The visibility tag normally hides the tool in that case; this error guards the path where a client invokes the tool by name despite it not being advertised.
Requirements
Only available in managed git mode. Set
MARKDOWN_VAULT_MCP_GIT_REPO_URL and a working
MARKDOWN_VAULT_MCP_GIT_TOKEN (with the
MARKDOWN_VAULT_MCP_GIT_USERNAME appropriate for your provider;
see the Git Integration guide).
Link Graph¶
Cold-start blocking
Calls to get_backlinks, get_outlinks, get_similar, get_context, and get_connection_path during a cold-start background FTS build block via the tool-layer needs_queryable decorator. If the build takes longer than MARKDOWN_VAULT_MCP_BUILD_TIMEOUT_S (default 60 s), the tool returns IndexUnavailableError(reason="timeout"). The same exception fires with reason="build_failed" if a scheduled background build ran and failed; read get_index_status's error field for the captured diagnostic. The decorator also remaps a SQLite OperationalError from the handler call to IndexUnavailableError(reason="broken") (corruption / I/O failure / unknown codes) or reason="busy" (SQLITE_BUSY/LOCKED, lock contention); inspect the exception's __cause__ for the underlying SQLite error. Poll get_index_status to observe build state without blocking.
get_backlinks¶
Find all documents that link to a given document.
Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
path |
string | required | Relative path to the target document |
limit |
int | null |
Maximum number of backlinks to return. Omitted (the default) returns all. |
wait_for_pending_writes |
bool | false |
Block until the IndexWriter drains before answering, then report freshness via _meta.index_stale (see the Index freshness on read tools note at the top of this page). |
Returns: List of documents containing links to the given path. Each entry has source_path, source_title, link_text, link_type, fragment, and raw_target fields. Index freshness is reported in _meta.index_stale (see the freshness note at the top of this page).
get_outlinks¶
Find all links from a document, with existence check.
Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
path |
string | required | Relative path to the source document |
limit |
int | null |
Maximum number of outlinks to return. Omitted (the default) returns all. |
wait_for_pending_writes |
bool | false |
Block until the IndexWriter drains before answering, then report freshness via _meta.index_stale (see the Index freshness on read tools note at the top of this page). |
Returns: List of link targets with an exists field indicating whether the target document is in the vault. Each entry has target_path, link_text, link_type, fragment, raw_target, and exists fields. Index freshness is reported in _meta.index_stale (see the freshness note at the top of this page).
get_broken_links¶
Find all links across the vault pointing to non-existent documents.
Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
folder |
string | null |
Optional folder filter; only checks links from documents in this folder |
Returns: List of entries with source_path, source_title, target_path, link_text, link_type, fragment, and raw_target fields.
get_similar¶
Find semantically similar notes by document path. Requires embeddings to be built.
Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
path |
string | required | Relative path to the document |
limit |
int | 10 |
Maximum files to return |
chunks_per_file |
int | server default (2) |
Maximum number of matching sections returned per file. Overrides MARKDOWN_VAULT_MCP_CHUNKS_PER_FILE for this call. 0 is rejected. |
wait_for_pending_writes |
bool | false |
Block until the IndexWriter drains before answering, then report freshness via _meta.index_stale (see the Index freshness on read tools note at the top of this page). |
Returns: List of grouped similar-document dicts ranked by cosine similarity, one entry per file with up to chunks_per_file best-matching sections. Each entry contains: path, title, folder, score (max section score), search_type ("semantic"), frontmatter, and sections (a list of {heading, content, score} dicts sorted by score then document order). Index freshness is reported in _meta.index_stale (see the freshness note at the top of this page).
Grouped result shape
Returns one entry per file with up to chunks_per_file best-matching sections. Default is 2 sections per file; pass chunks_per_file=1 for compact dossiers.
get_recent¶
Get the most recently modified notes.
Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
limit |
int | 20 |
Maximum results to return |
folder |
string | null |
Optional folder filter; only returns notes from this folder (such as "Journal") |
Returns: List of notes with Unix timestamps (modified_at as float), sorted by modification time (newest first).
get_context¶
Get a consolidated context dossier for a note. Combines backlinks, outlinks, similar notes, folder peers, tags, and modification time into a single response.
Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
path |
string | required | Relative path to the document |
similar_limit |
int | 5 |
Max similar files to include. Pass 0 to skip the similarity lookup (such as when stats shows semantic_search_available=false) |
link_limit |
int | 10 |
Max backlinks and outlinks to include each |
wait_for_pending_writes |
bool | false |
Block until the IndexWriter drains before answering, then report freshness via _meta.index_stale (see the Index freshness on read tools note at the top of this page). |
Returns: Object with path, title, folder, frontmatter, modified_at, backlinks, outlinks, similar, folder_notes, and tags fields. The similar list contains grouped result dicts, one entry per file with up to chunks_per_file best-matching sections (default 1 for get_context to keep dossiers compact). Index freshness is reported in _meta.index_stale (see the freshness note at the top of this page).
Grouped similar shape
Each similar entry contains path, title, folder, score, search_type, frontmatter, and sections (a list of {heading, content, score} dicts). get_context defaults to one section per file for compact dossiers; search and get_similar default to 2.
get_orphan_notes¶
Find all notes with no inbound or outbound links (isolated documents that may need cross-referencing).
Returns: List of NoteInfo objects (path, title, folder, frontmatter, modified_at, kind), ordered by path. Returns ALL orphans with no limit; check stats.orphan_count before calling on large vaults.
get_most_linked¶
Find the most-linked-to notes in the vault, ranked by backlink count.
Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
limit |
int | 10 |
Maximum results to return |
Returns: List of {"path": "...", "backlink_count": N} entries.
get_connection_path¶
Find the shortest path between two notes via BFS on the undirected link graph (max 10 hops).
Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
source |
string | required | Relative path to the starting document |
target |
string | required | Relative path to the target document |
max_depth |
int | 10 |
Maximum hops to search (clamped to [1, 10]) |
wait_for_pending_writes |
bool | false |
Block until the IndexWriter drains before answering, then report freshness via _meta.index_stale (see the Index freshness on read tools note at the top of this page). |
Returns: Object with found (bool), path (ordered list of document paths from source to target), and hops (number of edges, or -1 if not found). Index freshness is reported in _meta.index_stale (see the Index freshness on read tools admonition at the top of this page).
get_history¶
List commits that touched a note or attachment (or the whole vault) within an optional time window, up to a maximum count. Only available for git-backed vaults.
Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
path |
string | null |
Relative vault path (such as "notes/alpha.md" or "assets/diagram.png"). May be a .md note or a configured attachment extension (png, pdf, svg, …). Omit for vault-wide history. An unsupported extension is rejected. |
since |
string | null |
ISO 8601 datetime string ("2026-04-01T00:00:00") or git date expression ("1 week ago"). Passed as --since to git log. Inclusive at the boundary. |
until |
string | null |
ISO 8601 datetime string or git date expression, passed as --until to git log. Combined with since to bound a window. Inclusive at the boundary. |
limit |
int | 20 |
Maximum number of commits to return. Capped at 100. |
Returns: Object with commits (list of commit entries, newest-first) and total (count; always equals len(commits) and does NOT indicate how many commits exist beyond the limit cap). The envelope keeps the structured payload self-describing on the wire instead of relying on FastMCP's auto-wrapping result key. Each entry in commits contains:
| Field | Type | Description |
|---|---|---|
sha |
string | Full 40-character commit SHA |
short_sha |
string | 7-character abbreviated SHA |
timestamp |
string | ISO 8601 author timestamp |
author |
string | Author name and email |
message |
string | First line of the commit message |
paths_changed |
list[string] | Files touched by the commit. Populated for vault-wide queries (path=null); always empty for single-note queries, since the path is already determined by the query arguments (callers know which file the commit touched without needing it echoed back). |
Raises: ToolError if path is invalid or uses an unsupported extension.
get_diff¶
Return the diff of a specific note or attachment between a reference point and HEAD. Only available for git-backed vaults.
Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
path |
string | required | Relative vault path (such as "notes/alpha.md" or "assets/diagram.png"). May be a .md note or a configured attachment extension (png, pdf, svg, …). An unsupported extension is rejected. |
since_sha |
string | null |
A commit SHA (full or abbreviated, at least 4 hex digits) to diff from. Mutually exclusive with since_timestamp. |
since_timestamp |
string | null |
ISO 8601 datetime string, resolved via git rev-list --before=<ts> -1 HEAD to the most recent commit at or before that instant. Boundary is inclusive: a commit whose committer date equals since_timestamp IS the resolved ref. Mutually exclusive with since_sha. |
per_commit |
bool | false |
When false, return a single unified diff. When true, return one diff per intervening commit, newest-first. |
limit |
int | null |
Applies only when per_commit=true. Caps the number of commits returned to the limit most recent ones. Clamped to [1, 100]. null = unbounded (still bounded by the since..HEAD range). Silently ignored when per_commit=false. |
Exactly one of since_sha / since_timestamp must be supplied.
Returns:
per_commit=false: object withdiff(string), the unified diff from reference to HEAD. For a binary attachment, this is agit diff --statsize/rename summary (such asassets/x.png | Bin 1234 -> 5678 bytes); for a text attachment (.svg,.csv, …) or.mdnote, it is a full unified patch. May include[diff truncated: N bytes omitted]if output exceeds 50 KB.per_commit=true: object withcommits(list of per-commit entries, newest-first, each containingsha,short_sha,timestamp,message, anddiff) andtotal(count; always equalslen(commits)and does NOT indicate how many commits exist beyond thelimitcap). The envelope keeps the structured payload self-describing on the wire instead of relying on FastMCP's auto-wrappingresultkey.
Raises: ToolError if parameters are invalid, the reference commit is not found, or the path uses an unsupported extension.
One-Time Transfer Links¶
Transfer tools mint short-lived capability URLs so large files can move between the vault and a browser or another service without inflating the LLM context window. The unguessable token in the URL is the authorization; no separate Authorization header is needed.
HTTP/SSE transport only
Transfer tools require a running HTTP or SSE server with MARKDOWN_VAULT_MCP_BASE_URL set. They are not available on stdio transport.
Write tool visibility
create_download_link is available in read-only mode. create_upload_link is a write tool and is hidden when MARKDOWN_VAULT_MCP_READ_ONLY=true.
create_download_link¶
Mint a one-time capability URL to download a vault note or attachment. The file must exist at link-creation time. The URL can be fetched exactly once; after a successful download the token is consumed. A failed or interrupted download does not consume the token.
Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
path |
string | required | Relative path to the vault file (note or attachment). The file must exist; a missing path raises an error immediately |
ttl_seconds |
int | server default (MARKDOWN_VAULT_MCP_TRANSFER_TTL_DEFAULT_S) |
Token lifetime in seconds. Clamped to MARKDOWN_VAULT_MCP_TRANSFER_TTL_MAX_S. Omit to use the server default |
Returns:
{
"url": "https://mcp.example.com/transfer/...",
"path": "notes/report.md",
"expires_at": "2026-06-05T14:00:00+00:00",
"expires_in_seconds": 3600
}
Example usage:
{"path": "assets/diagram.pdf", "ttl_seconds": 600}
Then in a terminal:
curl "https://mcp.example.com/transfer/<token>" -o diagram.pdf
Read-lazy
The file is read from disk at fetch time, not at link-creation time. If the file is modified between link creation and download, the downloader receives the version current at fetch time.
create_upload_link¶
Mint a one-time capability URL to upload bytes to a fixed, pre-validated destination path in the vault. The destination path is decided at link creation; the uploader sends raw bytes via POST (or PUT as an alias). The upload commits via the normal write path (traversal and extension validation, size cap, index update, and git-commit callback).
Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
path |
string | required | Destination path in the vault. Validated for path traversal and allowed extension at link-creation time. May name a new or existing path; an existing file is overwritten on upload |
ttl_seconds |
int | server default (MARKDOWN_VAULT_MCP_TRANSFER_TTL_DEFAULT_S) |
Token lifetime in seconds. Clamped to MARKDOWN_VAULT_MCP_TRANSFER_TTL_MAX_S. Omit to use the server default |
Returns:
{
"url": "https://mcp.example.com/transfer/...",
"path": "assets/upload.pdf",
"expires_at": "2026-06-05T14:00:00+00:00",
"expires_in_seconds": 3600
}
Example usage:
{"path": "assets/uploaded-diagram.pdf"}
Then in a terminal:
curl -X POST --data-binary @local-diagram.pdf \
"https://mcp.example.com/transfer/<token>"
Raw body, not multipart
The upload endpoint expects the raw file bytes as the request body. Do not use multipart/form-data; send the content directly (curl's --data-binary flag does this correctly).
One-time
The token is consumed on the first successful upload. A transient failure (network error, size limit exceeded) does not consume the token; retry is permitted until the TTL expires.
MCP Apps¶
These tools power the browser-based vault explorer views. See the MCP Apps guide for details.
browse_vault¶
Open the vault explorer SPA. Optionally focus on a specific note and view.
Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
path |
string | null |
Note path to focus on |
view |
string | null |
View to open: context, graph, browse, or note |
Returns: For Apps-capable clients, opens the interactive SPA. For other clients, returns a text summary.
show_context¶
Open the Context Card view for a specific note, showing backlinks, outlinks, similar notes, tags, and folder peers.
Parameters:
| Parameter | Type | Description |
|---|---|---|
path |
string | Relative path to the document |
Returns: For Apps-capable clients, opens the Context Card. For other clients, returns the context dossier as text.