Building a Personal MCP Server
I wanted my AI to know more about me than the built-in personalization does, which is really just a collection of data points. So I built my own personal MCP server to be that layer, and I maintain it myself. Here’s how it went.
How I approached it
Strip away the protocol and an MCP server is a plain webserver: it exposes an endpoint that lists the tools it offers, and clients call them. So the real work was never the protocol, it was deciding what sits behind that endpoint.
claude.ai Claude Code …any MCP client
│ │ │
└───────────────┼──────────────────┘
│ one front door — OAuth
┌───────▼────────┐
│ auth proxy │
└───────┬────────┘
│
┌─────────▼─────────┐
│ personal-mcp │ ← the layer
│ service registry │
└─────────┬─────────┘
│
┌──────────┬───────┴───────┬───────────┐
│ │ │ │
┌──▼──────┐ ┌─▼───────┐ ┌─────▼───┐ ┌─────▼───┐
│ Mealie │ │ Immich │ │Paperless│ │ media │
└─────────┘ └─────────┘ └─────────┘ └─────────┘
recipes photos documents shows…
Each piece is a service, a small contract: what it’s called, what credentials it needs, how it registers its tools. One registry lists them all, and adding a new part of myself is a folder and a line.
The plan is for this to be the single front door for all my services, not just a one-off for recipes.
My first tenant
Recipes were always a hassle for me. I’d find one online, want to adjust it, and ask Claude about the tradeoffs of each change — then I’d have to track all those changes and amend them in Mealie by hand. I wanted Claude to just do that part, since it already has the context for the change. That’s why recipes became the first tenant.
When a client connects, the Mealie tenant hands it this set of tools:
recipes mealie_search_recipes find a recipe
mealie_get_recipe read one in full
mealie_create_recipe write a new one, in my style
mealie_update_recipe change an existing one
meal plan mealie_get_meal_plan see what's scheduled
mealie_create_mealplan_entry put a meal on a day
mealie_remove_mealplan_entry take one off
shopping mealie_get_shopping_lists list my shopping lists
mealie_get_shopping_list_items read what's on one
mealie_add_shopping_item add a single item
mealie_add_recipe_to_shopping_list add a whole recipe's ingredients
mealie_check_shopping_item tick something off
vocabulary mealie_list_vocabulary list known foods and units
mealie_update_vocabulary rename or fix one
mealie_merge_vocabulary fold duplicates together
So now I just chat to Claude in plain English. We work out the recipe together, I understand it as well as I want to, and once it’s settled the tedious part — writing it all into Mealie — takes care of itself. I could have Claude handle my meal plans and shopping lists the same way since those tools are exposed too, I just haven’t wired that into how I work yet.
The hard part was letting it in
A personal layer is useless if no AI can reach it, so making it reachable and safe ended up being most of the work.
I hand-rolled the auth first, a few hundred lines doing the OAuth dance myself. I fought it for a while, deleted it, and handed the whole job to a dedicated auth proxy. Then a quieter problem: for a while the connection never actually worked, because the server was closing its response stream before it finished writing the reply. The AI got back an empty 200, so it looked connected when it wasn’t.
The one that stuck with me came once it was live on the internet. I asked whether it was actually secure and went looking, and the auth was letting everyone through. It checked that a valid login happened, never that the login was mine. There was no list of who’s allowed in.
Authentication versus authorization, textbook stuff, and I walked straight into it. It matters more here because the thing behind the door is my actual life, so anyone who gets in is reading it. The door has to only open for me.
From here on out
I’m hoping to keep this going. The hard parts are done now, so from here it’s mostly hooking in new services. Each one is another piece of me the AI gets to know, instead of starting over every time I switch to a new one.