Neural Tech Daily
ai-tutorials

Build a Personal AI Knowledge Base with Obsidian and Claude via MCP: A Tutorial

End-to-end walkthrough: write a Python MCP server that exposes an Obsidian vault read-only to Claude Desktop, then ask Claude to summarise, connect, and draft notes.

Updated ~13 min read
Share
Model Context Protocol specification landing page showing the host-client-server architecture this tutorial implements for an Obsidian vault

Image: Model Context Protocol — Build an MCP server, used for editorial coverage of the protocol this tutorial implements.

What you’ll build

By the end of this tutorial, Claude Desktop will be able to read every Markdown note inside an Obsidian vault: list notes, fetch a note’s contents by title, and search across the vault, all without uploading any of those notes to a third-party service. You write a small Python MCP server that runs locally on your machine, register it with Claude Desktop via one config-file edit, restart the app, and then ask Claude questions in natural language that resolve against your own notes.

The whole flow runs locally over stdio. Claude Desktop launches your server as a subprocess, talks to it via JSON-RPC messages over stdin / stdout per the MCP specification (current revision dated 2025-11-25) 1 , and your server reads the vault directory using the standard Python file APIs. No cloud sync of the vault. No third-party plugin. Read-only by design, so even if Claude misunderstands a request, nothing in the vault changes.

You should be comfortable with Python 3.10 or later, virtual environments, and editing a JSON config file. Prior MCP experience is not required: the canonical Python MCP server is under fifty lines, and the FastMCP helper does the JSON-RPC plumbing.

What an MCP server actually is

The Model Context Protocol defines three roles: hosts are LLM applications such as Claude Desktop that initiate connections, clients are the connectors inside the host, and servers are external processes that provide context and capabilities to the host 1 . Servers expose three kinds of capabilities: resources (file-like data that can be read by the client), tools (functions the model can call, with user approval), and prompts (templated workflows) 1 .

For an Obsidian vault, two tools and one search tool are enough:

  • Tool list_notes: returns the names of every .md file in the vault.
  • Tool read_note: given a note title, returns the Markdown content.
  • Tool search_notes: given a search string, returns the titles of notes that contain it.

Tools rather than resources, because tools are explicitly invoked by the model and surface in the Claude Desktop UI as approvable actions per the MCP specification’s tool-safety principles 1 . Resources work too, but for an ad-hoc personal knowledge base, tools give the cleaner approval prompt on each call.

Prerequisites

The setup itself is small; the prerequisites are where most of the friction sits.

  • An Obsidian vault with at least a handful of Markdown notes in it. A vault is a folder on the filesystem containing notes, attachments, and a hidden .obsidian configuration folder. Obsidian’s documentation describes the vault as the unit of organisation for notes 2 . Notes are plain .md files, which is what makes this whole pattern work: nothing proprietary to parse.
  • Claude Desktop installed and updated. Download from Claude’s site if you have not already 3 .
  • Python 3.10 or later. The MCP Python SDK requires 3.10 minimum, and the SDK itself must be 1.2.0 or higher per the quickstart 4 .
  • uv installed. uv is the Astral Python package and project manager; uvx ships with it 5 . Install with curl -LsSf https://astral.sh/uv/install.sh | sh on macOS or Linux, or powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex" on Windows 4 .
  • About 30 minutes.

The tutorial is written for macOS and Windows. The Model Context Protocol quickstart notes that Claude Desktop is not yet available on Linux 4 ; Linux users can still run the server and connect via an alternate MCP client.

Step 1: locate your Obsidian vault path

Open Obsidian, hover over the vault name in the lower-left, and the absolute path appears. Common defaults look like /Users/you/Documents/MyVault on macOS and C:\Users\you\Documents\MyVault on Windows, though the path depends entirely on where you created the vault. Obsidian does not impose a fixed location 6 .

Copy this path. You will paste it into the server code and the Claude Desktop config later. Both places need an absolute path; relative paths will not survive the subprocess launch.

Obsidian homepage banner showing the knowledge-base application whose vault this tutorial exposes to Claude

Image: Obsidian homepage, used for editorial coverage of the vault model this tutorial reads from.

Step 2: scaffold the MCP server project

In a terminal, away from the vault folder itself, create a project directory and install the MCP SDK. The PyPI package is mcp; the [cli] extra pulls in the development tooling 7 .

uv init obsidian-mcp
cd obsidian-mcp
uv venv
source .venv/bin/activate   # Windows: .venv\Scripts\activate
uv add "mcp[cli]"
touch obsidian_server.py    # Windows: new-item obsidian_server.py

The project skeleton is intentionally small: one Python file, one config edit, no framework scaffolding.

Step 3: write the read-only vault server

Open obsidian_server.py and paste the following. Replace the VAULT_PATH value with the absolute path to your vault from Step 1.

"""Read-only MCP server exposing an Obsidian vault to Claude Desktop."""

import sys
from pathlib import Path

from mcp.server.fastmcp import FastMCP

# Absolute path to the Obsidian vault. Replace with your own.
VAULT_PATH = Path("/Users/you/Documents/MyVault")

mcp = FastMCP("obsidian-vault")


def _vault_files() -> list[Path]:
    """Return every Markdown file inside the vault, excluding .obsidian config."""
    return [
        path
        for path in VAULT_PATH.rglob("*.md")
        if ".obsidian" not in path.parts
    ]


def _resolve_note(title: str) -> Path | None:
    """Resolve a note title to a vault-relative path.

    Accepts the bare title (``Daily 2026-05-20``) or the full filename
    (``Daily 2026-05-20.md``). Returns None when no match is found.
    """
    stem = title[:-3] if title.endswith(".md") else title
    for path in _vault_files():
        if path.stem == stem:
            return path
    return None


@mcp.tool()
def list_notes() -> str:
    """List every Markdown note in the vault, one title per line."""
    titles = sorted(path.stem for path in _vault_files())
    if not titles:
        return "Vault contains no Markdown notes."
    return "\n".join(titles)


@mcp.tool()
def read_note(title: str) -> str:
    """Return the Markdown contents of a single note.

    Args:
        title: Note title, with or without the .md extension.
    """
    path = _resolve_note(title)
    if path is None:
        return f"Note not found: {title}"
    try:
        return path.read_text(encoding="utf-8")
    except OSError as exc:
        print(f"read_note error for {title}: {exc}", file=sys.stderr)
        return f"Could not read note: {title}"


@mcp.tool()
def search_notes(query: str) -> str:
    """Search every note for a case-insensitive substring.

    Args:
        query: Substring to search for inside note bodies and titles.
    """
    if not query.strip():
        return "Empty query. Provide at least one non-whitespace character."
    needle = query.lower()
    hits: list[str] = []
    for path in _vault_files():
        try:
            body = path.read_text(encoding="utf-8")
        except OSError:
            continue
        if needle in path.stem.lower() or needle in body.lower():
            hits.append(path.stem)
    if not hits:
        return f"No notes matched: {query}"
    return "\n".join(sorted(hits))


if __name__ == "__main__":
    mcp.run(transport="stdio")

Four things worth flagging in this code:

  • No writes. The server exposes three read tools and nothing else: no write_note, no delete_note, no rename_note. Adding write tools later is a one-line addition, but starting read-only is the safer default while you learn how Claude calls the tools.
  • The .obsidian filter in _vault_files. Obsidian stores its per-vault configuration in a hidden .obsidian folder inside the vault root 8 ; skipping it keeps community-plugin JSON files out of search results.
  • print(..., file=sys.stderr) for errors. The MCP Python quickstart warns explicitly: never write to stdout in a stdio server, because stdout carries the JSON-RPC messages and any extra bytes corrupt the protocol 4 .
  • mcp.run(transport="stdio"). Claude Desktop talks to local servers over stdio per the protocol’s transport definitions 1 .

Run the server once to confirm it starts cleanly:

uv run obsidian_server.py

The process will appear to hang, which is correct. The server is waiting for JSON-RPC messages on stdin. Press Ctrl-C to stop it.

modelcontextprotocol slash python-sdk GitHub repository social card showing the SDK whose FastMCP helper the server in this tutorial uses

Image: modelcontextprotocol/python-sdk on GitHub, used for editorial coverage of the SDK this tutorial depends on.

Step 4: register the server with Claude Desktop

Claude Desktop reads its MCP server registrations from claude_desktop_config.json. The file path depends on the operating system, per the MCP user quickstart 9 :

  • macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
  • Windows: %AppData%\Claude\claude_desktop_config.json

Create the file if it does not exist. On macOS, the fastest path is code ~/Library/Application\ Support/Claude/claude_desktop_config.json in a terminal; on Windows, code $env:AppData\Claude\claude_desktop_config.json from PowerShell 4 .

Paste this content, replacing /ABSOLUTE/PATH/TO/obsidian-mcp with the absolute path to the project directory you created in Step 2:

{
  "mcpServers": {
    "obsidian-vault": {
      "command": "uv",
      "args": [
        "--directory",
        "/ABSOLUTE/PATH/TO/obsidian-mcp",
        "run",
        "obsidian_server.py"
      ]
    }
  }
}

If uv is not on Claude Desktop’s PATH at launch, replace "uv" with the absolute path output by which uv on macOS or where uv on Windows; the quickstart calls this out as a recurring pitfall 4 .

Save the file and fully quit Claude Desktop (Cmd-Q on macOS, right-click tray icon then Quit on Windows). Restart it. On launch, Claude Desktop reads the config, spawns your server as a subprocess, and the three tools become available inside the chat.

A small slider or tool-count badge near the message-composer surfaces the new tools. Per the MCP user quickstart, the UI elements only show up once at least one server is properly configured 9 .

Model Context Protocol Quickstart for Claude Desktop users showing the configuration surface this tutorial registers the obsidian-vault server against

Image: Model Context Protocol — Quickstart for Claude Desktop users, used for editorial coverage of the registration surface this step targets.

Step 5: three example workflows

The point of the integration is what you can do with it. Three workflows the cited setup pattern supports out of the box:

Workflow 1: summarise a note

Prompt Claude with:

Use the obsidian-vault tools to read the note titled “Q2 OKRs 2026” and give me a 5-bullet summary.

Claude will request approval to call read_note, you accept, and Claude returns the summary grounded in the actual note contents. The model never sees notes you did not ask for; only the one note returned by the tool call.

Workflow 2: find connections across notes

Prompt:

Use search_notes to find every note that mentions “retrieval-augmented generation” and tell me which two notes are most related to each other, with reasoning.

Claude calls search_notes, then read_note on each hit (with approval prompts each time), then synthesises a connections report. The pattern works because every tool call is a JSON-RPC round-trip against your local server; latency is filesystem-fast.

Workflow 3: draft a new note from existing context

Prompt:

Read my notes “Onboarding workflow” and “Hiring loop 2026”, then draft a new note titled “Onboarding refresh proposal” that proposes three changes based on what’s already in those two notes. Output the Markdown so I can paste it into Obsidian.

Because the server is read-only, Claude cannot write the new note to disk; it returns the Markdown in chat and you paste it into Obsidian yourself. That is the intended boundary at this stage. Adding a write_note tool later turns this into a one-step workflow, with the usual caveat that write tools deserve more careful approval discipline.

Troubleshooting checklist

Five issues account for most first-run failures, per the patterns documented in the MCP quickstart and the Python SDK README 4 7 :

SymptomLikely causeFix
Tools do not appear in Claude DesktopConfig-file JSON malformed, or command / args paths wrongValidate JSON with a linter; confirm absolute paths; check which uv
Server crashes on startupVault path wrong, or vault contains zero .md filesPrint VAULT_PATH from a uv run session; confirm the path exists
Claude calls return errorsPermission denied on vault folderConfirm read permission on every vault file; on macOS, grant Claude Desktop Full Disk Access if needed
Tool calls hang silentlyprint() to stdout corrupting the JSON-RPC streamReplace any print(...) with print(..., file=sys.stderr) or use logging
Config edits ignoredClaude Desktop not fully quit before restartCmd-Q on macOS rather than closing the window; check Activity Monitor / Task Manager

If the server appears registered but tools never execute, the Claude Desktop developer log is the next stop: open Settings, look for the developer-mode toggle, and the per-server stderr stream surfaces there.

What this pattern does not do

Honest scoping, since cited reviews flag MCP servers as easy to over-build:

  • No semantic search. The server does substring matching. For embedding-based retrieval over a vault, the right shape is a separate indexing tool that builds and queries a local vector store, surfaced as a fourth MCP tool.
  • No backlink traversal. Obsidian’s wikilink graph ([[Note name]] references) is not parsed; the model sees the raw Markdown including the wikilink syntax. Adding a get_backlinks tool is straightforward once you parse [[...]] patterns out of the body.
  • No write path. Adding write_note and delete_note tools is one extra tool definition each, but the read-only posture is the conservative default until you have tested the approval flow with the read tools.
  • No multi-vault support. The server points at one vault. Multi-vault support means parameterising VAULT_PATH from the tool call or registering one server per vault in the config.

The aggregated source consensus from the MCP quickstart and specification favours starting with the smallest useful server and iterating 1 4 .

Next steps

The server in this tutorial is the foundation, not the destination. Three natural extensions:

  1. Add a write_note tool that takes title and body arguments and writes a new .md file to the vault root. Workflow 3 then becomes one-step.
  2. Add a get_backlinks tool that parses [[...]] wikilinks across the vault and returns the inbound link list for a given note. This unlocks graph-style queries.
  3. Wrap the server in a Desktop Extension (.mcpb) so the install path becomes one-click for non-developer users on your team. The MCP specification and quickstart describe the packaging format 1 .

The read-only baseline is enough to make the vault genuinely useful from inside Claude Desktop today. Everything beyond is iteration.

How this article was made: an autonomous AI pipeline researched, drafted, fact-checked, and reviewed this piece, aggregating publicly-available information from the sources consulted below. AI (artificial intelligence) can make mistakes, so please cross-check the consulted sources before acting on anything here. Neural Tech Daily is not liable for decisions or outcomes based on this article.

Sources consulted

Anonymous · no cookies set

Report a problem with this article

Articles are produced by an autonomous AI pipeline; mistakes do happen. Tell us what's wrong and the editorial review will revisit the claim.

Category

Found this useful? Share it.