Obsidian MCP (pgvector + Ollama, self-hosted)
Server Configuration
Describes the environment variables required to run the server.
| Name | Required | Description | Default |
|---|---|---|---|
| CHUNK_SIZE | No | Approximate tokens per chunk (4-char heuristic) | 512 |
| OLLAMA_URL | No | Ollama server URL (required when provider is ollama) | |
| SECRET_KEY | Yes | Secret key for itsdangerous signer | |
| VAULT_PATH | Yes | In-container path to the Obsidian vault (default: /obsidian) | |
| DATABASE_URL | Yes | PostgreSQL connection string (e.g., postgresql+asyncpg://user:pass@host/db) | |
| CHUNK_OVERLAP | No | Token overlap between chunks | 0 |
| OPENAI_API_KEY | No | OpenAI API key (required when provider is openai) | |
| EMBEDDING_MODEL | No | Ollama model name (used when provider is ollama) | bge-m3 |
| MULTI_USER_MODE | No | Enable multi-user mode (requires SECRET_KEY) | false |
| OPENAI_BASE_URL | No | Base URL for OpenAI-compatible API | https://api.openai.com/v1 |
| MCP_SANDBOX_MODE | No | Registry-eval only. Skips DB, indexer, embedding provider, and /mcp auth so introspection works without external deps. Do not enable in production. | false |
| EMBEDDING_PROVIDER | No | Embedding provider: 'ollama' or 'openai' | ollama |
| EMBEDDING_DIMENSIONS | No | pgvector column width (must match model output) | 1024 |
| INDEX_INTERVAL_SECONDS | No | Periodic reindex cadence in seconds | 300 |
| OPENAI_EMBEDDING_MODEL | No | OpenAI embedding model name | text-embedding-3-small |
Instructions
Guidance the server publishes about itself, which clients place ahead of the tool catalog so the model reads it before choosing anything.
This server publishes no instructions, or was last inspected before Glama recorded them.
Capabilities
Features and capabilities supported by this server
Protocol revision2025-11-25
| Capability | Details |
|---|---|
| tools | {
"listChanged": false
} |
| prompts | {
"listChanged": false
} |
| resources | {
"subscribe": false,
"listChanged": false
} |
| experimental | {} |
Tools
Functions exposed to the LLM to take actions
| Name | Description |
|---|---|
| keyword_searchA | Full-text keyword search via PostgreSQL tsvector. Use this for exact identifiers, code symbols, proper nouns, or known phrases — anywhere semantic noise hurts. For conceptual or paraphrased queries, use semantic_search instead. Args: query: Keywords or phrase to match (websearch tsquery syntax: "foo bar", "foo OR bar", "-bar"). folder: Optional folder prefix (e.g. "Cards/", "Projects/"). limit: Maximum number of results (default 20). tags: Optional list of tag names; only notes carrying ALL listed tags match (e.g. ["project", "active"]). frontmatter: Optional dict of frontmatter key/value pairs; only notes whose JSONB frontmatter contains every pair match. Strict type matching — string "0" does not match integer 0 (e.g. {"status": "draft"}). |
| read_noteA | Read a note from the Obsidian vault by its relative path. Returns a structured result, not a rendered document. Metadata and note content sit in separate fields, so there is no envelope to parse and no textual procedure to get wrong — read the fields:
Budgets are per field, not per response: Round trips. A whole-note Args:
path: Vault-relative path to the note (e.g. "Cards/My Note.md")
section: Optional ATX heading to read instead of the whole note. Plain
text ("Balance Sheet"), a path-style chain ("Parent/Child") when the
heading appears under different parents, or a "#N" ordinal ("#7",
1-based document order) — the ordinal is the only form that can
address duplicate headings sharing the same parent. The outline
returned with a truncated note carries the ordinal for every
section. A bare "#N" always selects by position and is never
shadowed by a heading whose text happens to be "#N"; use
"Parent/#N" to reach such a heading by title.
offset: Character offset to start reading from (default 0). Use the
|
| list_notesA | List notes in a vault folder, sorted by most recently modified. Results come from the index, so a note that exists on disk but has not yet been picked up by the indexer will not appear (lag is bounded by the index interval, typically up to 5 minutes). Args: folder: Vault-relative folder path (e.g. "Cards/", "Projects/"). Empty for vault root. limit: Maximum number of results (default 50). tags: Optional list of tag names; only notes carrying ALL listed tags match (e.g. ["idea"]). frontmatter: Optional dict of frontmatter key/value pairs; strict type match (e.g. {"status": "active"}). |
| get_tagsA | List all tags used across the vault with note counts. Args: limit: Maximum number of tags to return (default 50) |
| get_recentA | Get recently modified notes. Args: limit: Number of recent notes to return (default 20). folder: Optional folder prefix to filter (e.g. "Projects/"). tags: Optional list of tag names; only notes carrying ALL listed tags match (e.g. ["meeting"]). frontmatter: Optional dict of frontmatter key/value pairs; strict type match (e.g. {"status": "active"}). |
| semantic_searchA | Vector similarity search over the vault's chunk embeddings. Use this for conceptual or paraphrased queries — anywhere exact word matching would miss the point. For exact identifiers, code symbols, proper nouns, or known phrases, use keyword_search instead. Each result is one note (deduped) with its best-matching chunk as a ~200-character preview.
Call Args: query: Natural language description of what you're looking for. limit: Maximum number of distinct notes to return (default 15). folder: Optional folder prefix (e.g. "Projects/"). tags: Optional list of tag names; only notes carrying ALL listed tags match (e.g. ["product"]). frontmatter: Optional dict of frontmatter key/value pairs; strict type matching — string "0" does not match integer 0 (e.g. {"status": "active"}). |
| create_noteA | Create a new markdown note in the Obsidian vault. Requires write permission — a See Refuses a path whose final component is a symlink, naming its target, so a write never lands on a note other than the one named; symlinked folders inside the vault work normally. The note is published no-clobber: the content is staged out of sight and
linked into place in one kernel-atomic step, so an existing file at Args: path: Vault-relative path for the new note (e.g. "Cards/New Topic.md"). The .md extension is added if missing. content: Full markdown content for the note, including any frontmatter. |
| edit_noteA | Edit an existing note in the Obsidian vault. Requires write permission — a See Four mutually exclusive modes (set at most one of append/find/section):
Frontmatter and the round trip. Read a note, edit the The round-trip guarantee covers a complete, unwindowed whole-note read
only — Section mode: what
Section mode resolves headings over the frontmatter-stripped body, exactly
as Two shapes refuse a section write outright, naming the problem and writing nothing:
Both refusals are asymmetric with reads on purpose: Flags:
Writes are atomic: the composed result is staged in the note's own directory,
flushed to disk, and published with a single same-directory rename, so a
crash mid-write cannot truncate the destination. The publish is optimistic,
not locked — the bytes this call read are compared against the file
immediately before that rename, so a note somebody else changed in the
meantime fails with Args:
path: Vault-relative path to the note.
content: New body (full replace), replacement text, text to append, or
section body.
append: If True, append content to the end of the note.
operation: Legacy mode selector; accepts "append" or "replace".
find: Exact text to find and replace.
section: ATX heading text identifying the section whose body to replace.
Use |
| get_vault_guideA | Returns a two-part guide for working with this Obsidian vault:
|
| get_backlinksA | Notes that link TO Resolved links only (dangling references are not counted as backlinks). Args: path: Vault-relative path to the target note (e.g. "Cards/Foo.md"). limit: Maximum results (default 50, hard cap 500). |
| get_linksA | Outgoing links from Useful for "what does this note depend on?" or finding broken references that need follow-up notes. Args: path: Vault-relative path to the source note. |
| get_neighborhoodA | The connected subgraph reachable from Use this when an agent needs the local cluster around a topic — e.g.
"summarize everything connected to this project". Prefer this over
Args: path: Vault-relative path to the seed note. depth: Maximum BFS depth (default 1, capped at 5). limit: Maximum distinct neighbor notes (default 50, hard cap 200). |
| find_relatedA | Semantically similar notes based on the source note's chunk embeddings, averaged then queried via pgvector. Independent of the link graph — useful when the source is sparsely linked
or when looking for thematic neighbors. For link-based exploration use
Args: path: Vault-relative path to the source note. limit: Maximum results (default 10, hard cap 50). |
| find_orphansA | Notes with zero incoming AND zero outgoing resolved links — useful for vault hygiene ("what's disconnected?") and cleanup decisions. Args: folder: Optional vault-relative folder prefix to scope the search (e.g. "Cards/"). limit: Maximum results (default 50, hard cap 500). |
| move_noteA | Move or rename a note inside the vault. Requires write permission — a Updates With The rewrites are planned before anything changes: if one would push a source note past the 10 MiB note limit the whole move is refused, naming that source, before any file is touched. That preflight is also bounded in aggregate: if the originals plus rewrites for all backlink sources would exceed 256 MiB in memory the move is refused before anything changes, naming the note count and the limit. The same preflight refuses the whole move, before the rename, when any
source it would rewrite — the moved note's own body included — contains a
fence opener indented by one to three spaces that nothing below it
closes. The refusal names each such source and where its opener sits. A
link under such an opener may be inside a list item's code block, which
this server does not parse, and a rewrite would mutate text whose
code-or-content status had to be guessed. Move with A rewrite can still fail after the move has committed. The move is one
rename and the rewrites follow it, so an I/O failure, a vault reassignment,
or a database that cannot be reached to confirm the assignment stops the
remaining rewrites — and the result then reads Writes are atomic. Either path is refused, naming the link's target, when
its final component is a symlink; symlinked folders inside the vault work
normally and the recorded paths are the real ones behind them. See
Args: from_path: Vault-relative path of the existing note. to_path: Vault-relative path of the destination. Must not exist. Parent directories are created automatically. rewrite_links: If True, also rewrite incoming wikilinks and embeds in source notes. Off by default — opting in is destructive (it modifies other notes' bodies). |
| delete_noteA | Delete a note from the vault. Requires write permission — a By default this is a soft-delete: the file is moved to
A vault filesystem that cannot perform that non-replacing rename into
With A path whose final component is a symlink is refused, naming its target, so a delete never removes a note other than the one named; symlinked folders inside the vault work normally. Dangling backlinks left behind by a delete are surfaced via
Args: path: Vault-relative path to the note. permanent: If True, unlink instead of soft-deleting. |
| set_frontmatterA | Mutate a note's YAML frontmatter without touching its body. Requires write permission — a Parses the existing frontmatter, merges in A malformed block is refused, never worked around. An unclosed line-1
fence, YAML that fails to parse, and YAML that is not a mapping ( Only an effective change writes. Re-serialization uses A path whose final component is a symlink is refused, naming its target, so the frontmatter of an unnamed note is never rewritten; symlinked folders inside the vault work normally. See Args: path: Vault-relative path to the note. updates: Mapping of keys to set. Use the empty dict (or omit) to skip. remove: List of keys to delete from the frontmatter. Missing keys are ignored (and, on their own, make the call a no-op rather than a write). |
| read_fileA | Read any file in the vault — including non-markdown (PDFs, images,
skill HTML/JS, data files). Peer to This is pure byte transport: the server does NOT extract or parse PDFs and cannot interpret binary bytes. Non-text/non-image files come back as an opaque base64 string intended for a client-side skill to decode — not as something the model can read directly. Encoding:
Files larger than Text results are additionally capped to a context-safe size: the cap bounds
the returned window, and a truncated read appends a short notice carrying
the Args: path: Vault-relative path to the file (e.g. "Reference Docs/spec.pdf"). encoding: One of "auto" (default), "text", or "base64". offset: Character offset to start a text read from (default 0). Use the value the truncation notice reports to continue. limit: Maximum characters to return for a text read. Only lowers the server cap; it cannot raise it. |
| write_fileA | Write a file into the vault — including non-markdown (e.g. save a
generated PDF or image). Requires write permission — a
No-clobber by default: writing over an existing file requires
The MCP transport also bounds the whole request body (sized so a base64
write at the cap always gets through). Base64 is therefore the always-safe
encoding: Args: path: Vault-relative destination path (e.g. "Outputs/report.pdf"). content: File contents — base64 string (default) or UTF-8 text. encoding: "base64" (default) or "text". overwrite: If True, replace an existing file. Off by default. |
| list_filesA | Browse the vault filesystem ( By default lists the immediate children of At most Args: folder: Vault-relative folder (default "." = vault root). pattern: Glob applied to file names (default "*"). recursive: If True, descend into subfolders. Off by default. limit: Maximum entries to return (default 200, hard cap 1000). |
| request_uploadA | Get a short-lived link a person can use to put a file into the vault.
Requires write permission — a No MCP client can hand a tool the bytes of a file the user is looking at,
and your shell cannot reach their machine. This mints a link bound to
exactly one destination path: hand it to the person you are helping, they
open it and pick a file, and it lands at The token lives in the URL's Single use, and no-clobber unless you ask otherwise. With
From a shell you can upload without the page:
Then call Args:
path: Vault-relative destination (e.g. "Attachments/photo.png").
overwrite: If True, allow replacing an existing file at |
| check_uploadA | Ask what happened to an upload link you minted with Returns one of Visibility is scoped to the principal that minted the link, not to one
credential row. For an API key that is the key itself. For OAuth it is the
whole grant family behind the access token you are calling with, so a handle
stays readable across the hourly token refresh that mints a new row. A
different API key, a different client, or a separate approval of the same
client reads as
Pass the Args:
upload_id: The |
| request_downloadA | Get a short-lived link a person can use to save a vault file. Peer to
Handy for anything The token lives in the URL's The link is bound to the file as it is now: if it is edited or replaced, the link stops working rather than serving different content than you described. Unlike an upload link it can be used more than once, so the person can preview and then save. From a shell: `curl -H "Authorization: Bearer " -o Args:
path: Vault-relative path of the file to share.
expires_in: Seconds until the link dies. Clamped to 60–3600; defaults
to |
| import_from_urlA | Fetch a file from a public https URL straight into the vault. Requires write permission — a The server does the fetching, so nothing passes through your context: a 20 MB PDF costs one tool call. Returns the path, size, sha256, MIME type and the final URL after any redirects. Only genuinely public addresses. This server sits on a private network
next to a database and other services, so the fetch is restricted: https
only — plain http is refused unless the operator has set
Size-capped at This tool shares the transfer tools' preflight, so it also refuses when the
server has no public origin configured ( Args:
url: Public https URL of the file.
path: Vault-relative destination (e.g. "Attachments/paper.pdf").
overwrite: If True, allow replacing an existing file at |
| delete_fileA | Delete a non-markdown file from the vault. Requires write permission — a By default this is a soft delete: the file moves to
With Refuses markdown files (use Args:
path: Vault-relative path to the file.
permanent: If True, unlink instead of moving to |
Prompts
Interactive templates invoked by user choice
| Name | Description |
|---|---|
No prompts | |
Resources
Contextual data attached and managed by the client
| Name | Description |
|---|---|
No resources | |
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/maxkuminov/obsidian-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server