← writing

Building an MCP for your own brain

I built a tiny MCP server that exposes my Obsidian vault, calendar, and inbox as tools, and Claude is suddenly the best search engine I own. Here is the architecture, the design choices, and why this approach quietly beats RAG-on-everything.

Last Tuesday I asked Claude what I had written about Postgres last month, and it pulled the exact note I was thinking of in about two seconds. The thing is, I have tried this before with vector databases and elaborate RAG pipelines, and it never felt this natural. The difference this time is that I stopped trying to pre-index my life and started letting the model fetch things on demand. The tool that made this click is MCP.

So I spent a weekend building a small MCP server for myself. One binary, three modules, running locally over stdio. It exposes my Obsidian vault, my Google calendar, and my email inbox as a handful of tools. That is the whole project. And honestly it is the most useful piece of personal software I have shipped this year.

The rough architecture

The server is a single Python process. Inside it there are three modules, each owning two or three tools:

  • notes: search_notes, read_note, write_note
  • calendar: list_events, find_free_slots, draft_event
  • email: search_inbox, read_thread, draft_reply

That is nine tools total. No vector store, no embeddings, no background indexer. Each tool is a thin wrapper around an API I already had access to: the Obsidian vault is just a folder of markdown files, the calendar is the Google API, and the inbox is IMAP plus the Gmail API for drafts.

Design choices that actually mattered

I made a few rules at the start and stuck to them.

Read-only for email by default. The server cannot send anything. It can search, read threads, and stage a draft in my Gmail drafts folder, but the final click to send is always mine. The same rule applies to calendar. Claude can draft an event with attendees and a proposed time, but it cannot put it on my calendar without me opening Gmail or Calendar and pressing the button. This sounds paranoid until you remember that one bad tool call could email your entire contact list.

One server, not three. I almost split this into three separate MCP servers, one per data source. I am glad I did not. Having everything in one process means cross-module tools can compose. Claude can search my notes for context, then draft a reply that references what it found, in a single turn.

Tool descriptions are the actual product. I rewrote each tool description maybe a dozen times. The description is what the model sees, and a vague description means a model that picks the wrong tool. Here is the one for search_notes that ended up working well:

Search the user's personal Obsidian notes by content and metadata.
Returns a ranked list of matching notes with title, path, last modified
date, and a 200-character snippet around the match. Use this when the
user refers to something they wrote, thought about, or saved. Prefer
short keyword queries (2-4 words). For exact phrases, wrap in quotes.
Does NOT read full note contents, use read_note for that.

Notice what is in there: input format hints, what comes back, when to use it, and a pointer to the next tool. That last line saves a round trip almost every time.

The surprises

Two things surprised me.

First, Claude is much better at finding the right note than the built-in Obsidian search. I think this is because it can reformulate the query a few times, try synonyms, and rank results based on what I actually asked for rather than raw keyword overlap. The Obsidian search treats my query as a literal string. Claude treats it as an intent.

Second, I almost never want the model to read everything. The pattern that emerges naturally is search, then read one or two notes, then answer. Pre-indexing my whole vault into a vector DB would have meant the model getting fifty fuzzy chunks every time. With tools, it pulls the two notes it actually needs and works from there.

Why this beats RAG on everything

RAG-on-everything tries to flatten your life into a single embedding space. It works, sort of, but it loses structure. A note is not the same kind of object as an email thread, and pretending they live in the same vector neighborhood gives you mediocre answers about both.

Tools keep the structure. The model knows that search_inbox and search_notes are different actions with different return shapes, and it picks the right one based on what you asked. It also fetches just-in-time, so you are not paying to embed and store data you will never query.

The practical upshot is that adding a new data source is now a fifteen-minute job. Define two or three tools, write good descriptions, register them. No re-indexing, no schema migration, no embedding cost.

A note on privacy

This whole thing runs on my laptop. The MCP server talks to Claude Desktop over stdio, which means nothing leaves the machine except the conversation itself. My notes, my calendar entries, and my email bodies are read locally and only the relevant snippets get sent up as tool results. That is a meaningful step up from uploading everything to a cloud RAG service.

The takeaway

I keep seeing pitch decks for new personal AI chat UIs, and I think they are missing the point. The chat UI is fine. The thing that turns a chatbot into an assistant is the data you plug into it. The killer app for personal AI is not a new chat UI, it is a small set of well-designed tools pointed at the data you already have. If you have a weekend, build one for yourself. You will use it every day.

Want more like this?

Occasional, opinionated, no listicles.
all writing →