[feature] Ship a first-class stdio MCP server so non-Agent-Skills clients can reach the vault
Disclosure: I maintain omind, a separate Apache-2.0 project solving an adjacent problem (durable agent memory in a plain-Markdown Obsidian vault). I read claude-obsidian's source before filing and I'm linking omind's implementation as a worked reference for the design — not proposing you adopt its code. Per CONTRIBUTING's evidence rule, every claim below points at a specific file.
Problem
claude-obsidian is reachable only from Agent Skills hosts. bin/setup-multi-agent.sh
links the portable skills into Codex, OpenCode, and Gemini; Cursor and Windsurf use
workspace-local skill discovery. skills/wiki/references/mcp-setup.md covers configuring
third-party MCP servers alongside the vault.
What's missing is claude-obsidian itself speaking MCP. That leaves out every MCP client
that isn't an Agent Skills host — Claude Desktop, Zed, JetBrains, anything built on the
Agent SDK, and any custom agent that already speaks MCP and nothing else. Those clients
can't query the vault at all today, even read-only, despite the vault being plain files
and the retrieval stack (scripts/retrieve.py, scripts/bm25-index.py) already being
importable Python with a clean JSON contract.
The gap is distribution, not capability. retrieve.py already emits exactly the shape an
MCP tool would return — chunk_id, page_path, absolute_path, bm25_score,
rerank_score, snippet.
Proposed solution
Ship a stdio MCP server as an optional surface — python3 scripts/claude-obsidian.py mcp --vault PATH.
Start read-only, which sidesteps the hard part entirely:
| Tool | Backed by |
|---|---|
retrieve |
scripts/retrieve.py (already JSON-out) |
read_page |
transaction.read_vault_regular (bounded, symlink-safe) |
lint |
claude_obsidian.lint_engine (already as_of-deterministic) |
doctor |
existing readiness report |
Writes, if ever added, should not get a direct tool. They should return a transaction
bundle for the host to inspect and apply through the existing
transaction inspect / transaction apply path, so the one-recoverable-transaction
contract in skills/wiki/references/operation-transactions.md holds no matter which
client is driving.
For reference, omind's equivalent is
src/omind/server.py
(omind node) — a stdio MCP server over the vault store, ~500 lines including paging and
shutdown handling. One detail worth stealing regardless of whether you do this: it exits
cleanly when the client closes stdin. Depending on the SDK's file-wrapper stdio path cost
us a class of 40-minute hangs before we moved to fd readiness.
Alternatives considered
- Status quo — skills only. Fine if Agent Skills hosts are the intended universe. But the vault is explicitly "a normal directory of Markdown," and that portability argument applies to clients as much as to files.
- Point users at a generic filesystem/Obsidian MCP server. Works for raw reads, but
loses BM25, the contextual chunks, and the vault-selection rules in
claude_obsidian/paths.py— a generic server would happily treat the product checkout as a vault, which is exactly what your trust boundary is designed to prevent. - REST/HTTP API. More surface, needs auth, and no client asked for it.
Scope
- A new script (
scripts/<name>) - A new skill / agent
- Change to existing skill
- Change to plugin manifest / hooks / setup scripts
- Documentation only
Additive. Nothing existing changes.
Compatibility
- Behavior change for existing v1.x vaults? No — read-only, additive surface.
- New opt-in (
bin/setup-*.sh)? Yes —bin/setup-mcp.sh, following the existing preview-then---applypattern. - New dependency? Optional. The MCP Python SDK would be needed only when the server is actually launched, so the portable core keeps its stdlib-only posture. A hand-rolled JSON-RPC-over-stdio loop is also viable if a hard no-dependency rule applies — the protocol surface for four read-only tools is small.
Testing
Hermetic and offline. Drive the server in-process over a pipe pair against a tmp_path
fixture vault, then assert on the JSON-RPC exchange: initialize handshake, tools/list
matching the declared contract, tools/call for each tool, and clean exit on stdin EOF.
No network, no model server — retrieval already degrades to BM25-only without Ollama, and
retrieve.py already exits 10 when the index is missing, which the tests can pin.
Worth adding a case for vault-selection refusal: pointing the server at the product checkout must fail closed, same as every other command.
Additional context
- omind's MCP server:
src/omind/server.py - Its mesh/node docs:
docs/mesh.md
Happy to be told this is deliberately out of scope — the skills-first distribution model is a coherent choice and I may be reading a decision as an omission.
Source: AgriciDaniel/claude-obsidian