Open source · Python · MCP. This case examines published revision 4ac3c0f3. Its primary workflow uses a local stdio connection.
Notes as evidence an agent can cite
An Obsidian vault already has useful structure: Markdown, folders and meaningful filenames. I built an MCP server so an agent can find notes, read them and prepare new material while preserving that structure. This workflow requires neither moving the vault into a separate database nor placing every document in the initial prompt.
My contribution covers the file service, six MCP tools and packaging. The agent chooses operations and composes an answer; the server performs concrete filesystem work. That boundary makes it possible to inspect the source of a quotation and identify the file a proposed edit would change.
- AI clientUser request and tool selection
- MCP serverPython, stdio and VaultService
- Markdown vaultFiles and folders mounted into the container
The server does not select a model. The client receives note contents; using a cloud model can send that text to its provider.
Find a decision, then inspect its source
Consider a request to find the database decision for a history feature. When the folder is known, vault_glob identifies matching paths. vault_search finds text across the vault, and vault_read opens the selected document. A matching line locates evidence; the agent still needs the surrounding context.
{
"name": "vault_search",
"arguments": {"query": "PostgreSQL"}
}{
"matches": [{
"path": "Projects/decision.md",
"line": 2,
"content": "Use PostgreSQL for history."
}],
"total_files": 2
}
This result came from a check using two synthetic notes. The second line of Projects/decision.md contains the displayed text. total_files counts processed files, not matches. Line numbers are one-based. These details let the agent point to something the reader can verify.
Three implementation choices
Separate filesystem behavior from MCP
VaultService implements read, write, listing, glob, tree and text search. Thin MCP handlers pass arguments and return results. Filesystem behavior can be checked with temporary directories without an AI client. Missing-file errors become short messages that do not expose absolute host paths.
Use inspectable text search
Search reads Markdown and returns the path, line number and matching content. It supports case-sensitive matching, but does not compute embeddings or rank semantic similarity. There is no separate index to operate. The tradeoff is repeated file reading: this implementation does not replace a dedicated search engine for large vaults.
Make write behavior explicit
vault_write creates parent directories and writes the complete document, overwriting an existing file. It provides no automatic merge, version check or undo. For a first connection I use a vault copy with a read-only Docker mount. The write tool remains advertised, but the filesystem rejects modifications. Editing requires a deliberate writable mount and an independent file-history strategy.
Evidence in the implementation
While preparing this case, I called the current vault_glob, vault_search, vault_read and vault_write handlers on temporary notes in a container without network access. Search returned the line above, reading preserved its content, and writing created a separate draft. This checks handlers, not every desktop client or the network authorization flow.
The repository contains the MCP handlers, search implementation and result-shape test. Other tests describe Unicode, nested folders and overwrite behavior. I have not measured time saved or reductions in tool calls.
Current boundaries
This is a tool for a trusted local environment, not isolation between unrelated users. Path checks are not a substitute for handling symlinks and filesystem races. An SSE transport exists, but its authorization endpoint automatically approves requests, without a user login or consent screen. Public access requires further implementation and security review.
Try a separate vault
Build the image and prepare a test folder containing a few notes:
git clone https://github.com/tenqz/obsidian-agent.git
cd obsidian-agent
docker build -t obsidian-agent-mcp .
Use these process settings in a client supporting stdio, replacing the absolute path with your test folder. The read-only mount deliberately limits this example to reading:
{
"command": "docker",
"args": [
"run", "--rm", "-i", "--network", "none",
"-e", "MCP_TRANSPORT=stdio",
"--mount", "type=bind,src=/absolute/path/demo-vault,dst=/vault,readonly",
"obsidian-agent-mcp"
]
}
Start with: “Find PostgreSQL, show its path and line, then read the document; change nothing.” The Obsidian MCP setup article explains client integration. After checking read access, decide separately whether the workflow needs writes.