My Obsidian Quick Note MCP
I’ve been using Obsidian as my personal knowledge base for a while now — it’s where I dump notes from courses, ideas, and anything I want to remember later.
The problem: whenever I’d learn something useful in a Claude conversation, capturing it meant switching apps, creating a file, pasting content, formatting it… you get the idea. It was enough friction that I’d often just skip it.
So I built a small MCP Server with one job: let Claude write notes directly into my Obsidian vault via a single tool — create_quick_note.
My Vault Layout Link to heading
Before getting into the code, a quick look at how my vault is organized — the MCP server is shaped around this structure.
~/Documents/Vinny's Notes/
├── _QuickNotes/ # Staging area for AI-generated notes
│ ├── Go Goroutines.md
│ ├── SQL Joins.md
│ └── ...
└── ... # Other personal notes and folders
The underscore prefix on _QuickNotes is an Obsidian convention — it sorts the folder to the top of the file explorer and signals that it’s a special-purpose directory rather than a topic area.
The key design choice here: Claude can only write into _QuickNotes. It has no access to the rest of my vault. That folder is a landing zone — I review notes there and manually file or link them into my structured notes when they’re worth keeping. This keeps Claude’s writes scoped and reversible.
Project Setup Link to heading
This server uses FastMCP — the same library from the Getting Started with MCP article. You’ll need Python 3.10+ and uv.
uv init obsidian-mcp
cd obsidian-mcp
uv add fastmcp
The Server Link to heading
Create main.py. The setup is minimal — just a path to the vault and the FastMCP instance:
from datetime import datetime
from pathlib import Path
from fastmcp import FastMCP
VAULT = Path.home() / "Documents" / "Vinny's Notes"
QUICK_NOTES = VAULT / "_QuickNotes"
mcp = FastMCP("obsidian")
create_quick_note
Link to heading
@mcp.tool()
def create_quick_note(title: str, content: str, references: list[str] | None = None) -> str:
"""Create a quick note in the _QuickNotes directory of the Obsidian vault.
If a note with the same title already exists, the content is updated and
an entry is appended to the note's Edit History section.
Args:
title: The note title (used as the filename).
content: The body of the note in markdown.
references: Optional list of sources or links used when writing the note.
"""
QUICK_NOTES.mkdir(parents=True, exist_ok=True)
safe_title = title.strip()
note_path = QUICK_NOTES / f"{safe_title}.md"
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M")
refs_section = ""
if references:
ref_lines = "\n".join(f"- {r}" for r in references)
refs_section = f"\n## References\n{ref_lines}"
if note_path.exists():
existing = note_path.read_text()
history_marker = "\n## Edit History\n"
if history_marker in existing:
body, history_section = existing.split(history_marker, 1)
history_entries = history_section.strip()
else:
body = existing
history_entries = ""
new_history = f"- {timestamp}: content updated"
if history_entries:
new_history = history_entries + "\n" + new_history
full_content = f"# {safe_title}\n\n{content}{refs_section}{history_marker}{new_history}"
note_path.write_text(full_content)
return f"Quick note '{safe_title}' updated."
else:
full_content = f"# {safe_title}\n*Created: {timestamp}*\n\n{content}{refs_section}"
note_path.write_text(full_content)
return f"Quick note '{safe_title}' created."
if __name__ == "__main__":
mcp.run()
A few design decisions worth calling out:
Idempotent updates. If a note with the same title already exists, the tool rewrites the content rather than erroring or creating a duplicate. Claude can revisit and expand a note across multiple sessions without making a mess.
Edit history. On update, the tool appends a timestamped entry to an ## Edit History section at the bottom. After a few sessions you end up with a lightweight audit trail — useful when you come back to a note weeks later and want to know when it last changed.
Optional references. Pass a references list and the tool writes them into a ## References section. When I ask Claude to summarize something it found online, I include the source URL so the note stays traceable back to where the information came from.
Docstring as behavioral guidance. The docstring isn’t just for humans — Claude reads it too. The Args block tells Claude exactly what each parameter expects, so it fills them correctly without needing extra prompting on my end.
Connect to Claude Desktop Link to heading
Claude Desktop reads its MCP config from:
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
%APPDATA%\Claude\claude_desktop_config.json
Add an entry for the server:
{
"mcpServers": {
"obsidian": {
"command": "uv",
"args": ["run", "--directory", "/absolute/path/to/obsidian-mcp", "main.py"]
}
}
}
Restart Claude Desktop. The hammer icon in the chat interface confirms the tool is loaded.
A Day in the Life Link to heading
Here’s what a typical session looks like. Say I’m working through SQL window functions and want to lock in what I just learned.
I ask Claude to explain the concept:
“Explain SQL window functions to me — specifically
ROW_NUMBER,RANK, andDENSE_RANK.”
Claude walks me through the differences, gives examples, and I feel like it clicked. Rather than letting that slip away, I follow up:
“Save that explanation as a quick note called ‘SQL Window Functions’ in my Obsidian vault.”
Claude calls create_quick_note and writes the note to _QuickNotes/SQL Window Functions.md. The next time I open Obsidian, it’s sitting at the top of my file explorer ready to be filed:
# SQL Window Functions
*Created: 2026-04-08 14:32*
Window functions perform calculations across a set of rows related to the current
row, without collapsing them into a single output row like GROUP BY does.
## ROW_NUMBER
Assigns a unique sequential integer to each row within a partition. No ties —
every row gets a distinct number.
## RANK
Tied rows get the same rank. The next rank after a tie skips numbers.
Scores of 100, 100, 90 → ranks 1, 1, 3.
## DENSE_RANK
Same as RANK for ties, but the next rank does NOT skip.
Scores of 100, 100, 90 → ranks 1, 1, 2.
If I come back to the topic later and want to add something:
“Update my SQL Window Functions note to also cover
LEADandLAG.”
Because the tool is idempotent, Claude rewrites the content and appends a timestamp to the ## Edit History section — so I can see that the note was expanded, not just when it was first created:
# SQL Window Functions
...
## Edit History
- 2026-04-08 15:10: content updated
What’s Next Link to heading
This is a deliberately narrow server — one tool, one folder, writes only. That’s intentional. From here, a few directions worth exploring:
- Tagging on creation — extend
create_quick_noteto accept tags and write them into YAML front matter, so notes land in the right place in Obsidian’s graph view automatically. - Backlink-aware notes — if a note title matches an existing note in the vault, automatically add an Obsidian
[[wikilink]]in the body to connect them. - Semantic search — a companion read tool using embeddings so Claude can pull in related notes as context before writing a new one.
References Link to heading
- Model Context Protocol — Introduction
- FastMCP — Python framework for building MCP servers
- Obsidian — the note-taking app this server is built around