Every AI session used to start the same way. Who I am. Which project this is. What we did yesterday. Where the config lives. Then, ten minutes in, the actual work.
I juggle a day job in Azure and Kubernetes, a homelab, a consultancy (ZADO) and a couple of side projects. I use Claude and GitHub Copilot, on a Mac, a Linux box and my phone. That's a lot of context, and every assistant forgot all of it the moment I closed the window.
So in January I started building my own MCP server. Nine months later it has 244 tools. It checks my pipelines, reviews my code against my own standards, knows my design system, keeps my calendar and task boards, and remembers where I left off, from the desk or from my phone.
The surprise was where the real value turned out to be. The tools aren't the main win. The win is an assistant that remembers where I left off, knows what I know, follows me to my phone, and keeps all of it on hardware I own.
Why "Personal" Is the Important Word
Most write-ups treat an MCP server as an integration layer: connect the AI to GitHub, to Slack, to a database. That's useful, but vendors already ship those connectors.
What nobody ships is your context: your projects, your notes, your calendar, your coding standards, your design system. A personal server fills that gap. Mine started as a refactoring helper for my Python code. Today it's a second memory with a full DevOps toolbox attached.
A Few Words Before We Start
This article mentions a handful of tools from my setup. You don't need any of them to build your own, but here's what they are:
| Term | What it is |
|---|---|
| MCP | Model Context Protocol. An open standard that lets an AI assistant call external tools. |
| MCP server | A program that offers those tools. The assistant connects to it, sees a list of tools and calls them. |
| Tool | One function the AI can call, like "list today's appointments". |
| Connector | How the Claude apps (web, desktop, phone) attach to a remote MCP server. |
| FastMCP | A Python library for writing MCP servers. A decorator turns a function into a tool. |
| Nomad | HashiCorp's scheduler. It runs containers on my homelab servers, like a lighter Kubernetes. |
| Consul KV | HashiCorp's key-value store. I keep secrets there. |
| Traefik | The reverse proxy in front of everything. Handles TLS and checks the access token. |
| Redis | An in-memory database. Holds memories, sessions and saved work context. |
| SQLite FTS5 | Full-text search built into SQLite. Powers my knowledge search. |
| Gitea | Self-hosted Git server with issues, pull requests and CI (Gitea Actions), like a small GitHub. |
It Remembers Where I Left Off
A typical week: I stop a task on Thursday afternoon on the Mac, halfway through a change. Monday morning I sit at the Linux box with a different assistant. That used to mean scrolling git log, reopening files and explaining the whole thing again.
Now every session ends with a short note: what I was doing, and what comes next. The next session starts with a briefing built from that note. project_resume returns the project, the last seven commits, open PRs, the saved note and my next steps. A small shell script sends it as the first message of a Copilot session, and Claude reads the same data.
For things that aren't tied to one project, there's a memory store in Redis: decisions, preferences, "don't touch that volume config on the Nomad client again". I've learned this the hard way: the expensive part of AI-assisted work isn't the model's answer. It's re-establishing context before you can ask the question.
One Brain for Every Assistant
I used to keep separate instruction files for Claude and Copilot, and they drifted apart within weeks. Now the knowledge lives in the server, and every assistant reads from it.
The catalog holds 29 coding instructions, 28 skills, 55 agent definitions and my howtos. It's searched with SQLite FTS5. No embeddings, no vector database. Keyword search over a few hundred of my own documents is fast, and when it misses, I can see why. So when I ask "how did I set up canary deploys on Nomad?", the answer comes from my own howto, not a generic blog post.
My design system lives there too. My sites share a set of <i80-*> web components, and AI assistants used to guess at their attribute names and invent components that didn't exist. The design system site publishes two machine-readable files: a components.json with every component, attribute, default and example, and an llms.txt with the usage rules. The server indexes both and caches them on disk, so lookups keep working even when the cluster serving the site is down. Front-end code now uses my real components on the first try.
All of it works the same whether I'm in Claude Code on the Linux box or Copilot on the Mac.
What's Actually in the Toolbox
Memory and knowledge are the foundation. On top sit the tools, 244 of them over 37 areas. In a normal week, I use these:
| Area | What I ask for |
|---|---|
| Calendar | "What do I have tomorrow?" and "Add a call with the accountant on Friday at 10". |
| Gitea | "Anything failed overnight?" runs gitea_morning_check: failed pipelines, running jobs, open PRs. Create and comment on issues, create repos. |
| CI/CD | Read a failed workflow run step by step. Review workflow files for security and bad practice. |
| Containers | Test that a Dockerfile builds and the container starts. Check that an endpoint answers after a deploy. |
| Code review | Run my personal coding standards, PEP 8 via flake8 and black, duplicate detection with jscpd, and CVE scans of dependencies. |
| Risky code | analyze_git_hotspots combines git churn with complexity. A file that changes often and is complex is where the next bug lives. |
| Design system | Look up components, attributes and usage rules in my own design system. |
| Writing | Grammar and spelling via a self-hosted LanguageTool, in Danish and English. No text leaves my network. |
| Taskz | My task boards. A bigger job gets broken into tasks, and any session on any machine can pick up the next one. |
| Personas | Test personas with a name, a company, a role and a list of things they may and may not do. The assistant can test as any of them. |
| Browser | Open a page in a headless Playwright browser, click through it and take a screenshot. Handy right after a deploy. |
| Library docs | Fetch current documentation for a library via Context7, so the assistant codes against today's API and not the one from its training data. |
| New projects | Scaffold a Flask app with Tailwind, a Nomad job and a Gitea workflow, ready to deploy on my homelab. |
| Knowledge and memory | Search my howtos and coding instructions. Save decisions, work context and architecture decision records. |
A table of tools doesn't show what changes in practice, though. A conversation does.
Same Question, With and Without the Server
Below are six questions I ask regularly, each shown twice: once in a plain session, and once with my server connected. The project, pantry-api, is a dummy Flask app, and the numbers are made up. The tool behaviour is real.
1. "Anything that needs me this morning?"
Without the server, the assistant can only tell me how to find out:
> Anything that needs me this morning?
I don't have access to your Gitea instance. You can check open pull
requests with `tea pulls list` in each repository, and look at the
Actions tab for failed runs. Want a script that loops over your repos?
With the server, it just answers:
> Anything that needs me this morning?
⏺ gitea_morning_check()
One thing is red: pantry-api failed its last pipeline run. web-i80 is
building right now. There's one open PR, #12 "Bump fastmcp to 2.12".
23 repos checked. Want me to pull the log for the pantry-api failure?
The follow-up question matters most, because the conversation keeps going from there:
- "Yes, why did it fail?"
gitea_get_workflow_runreturns the failing step and its log. A new dependency broke the container build. - The fix. The assistant changes the Dockerfile.
validate_dockerbuilds the image and starts the container to prove it runs. - The safety net.
scan_dependencieschecks the new package for known CVEs before it goes anywhere. - Ship and verify. I push.
gitea_actions_statusshows the run go green, andcheck_endpointconfirms/healthanswers 200 after Nomad rolls out the new version. - Close the loop.
save_work_contextwrites down what changed and why.
I've done each of those steps by hand for years. The difference is that I no longer jump between the Gitea UI, a terminal, the Nomad UI and a browser. The context stays in one conversation, and the assistant sees the same log I do.
2. "Review this file before I open a PR"
Without the server, I get a solid but generic review:
> Review pantry/sync.py before I open a PR.
Looks reasonable overall. A few suggestions: line 31 exceeds 79
characters, line 44 uses a bare `except`, and you might consider adding
type hints. Consider adding tests for the error path.
With the server, the review uses my rules, not the internet's average:
> Review pantry/sync.py before I open a PR.
⏺ check_personal_standards(file_path="pantry/sync.py")
Score 71/100 (Fair). Two things I'd fix before the PR:
- Line 22 runs rsync through `shell=True`. Your standard is no shell
strings: pass a list instead, ["rsync", "-a", src, dst].
- Line 9 has an API key in the source. Move it to an environment
variable.
Quick win: replace the os.path.join calls with pathlib.
The generic review isn't wrong. It just doesn't know that I care more about a shell injection than a long line. This is the tool I'd miss first.
3. "Where should I refactor first?"
Without the server, the assistant guesses from what it can see:
> Where should I start refactoring in pantry-api?
models.py is the largest file and has the most recent changes, so it's
a natural place to start. Splitting it into smaller modules would help.
With the server, it combines git history with code complexity:
> Where should I start refactoring in pantry-api?
⏺ analyze_git_hotspots(project_path="pantry-api", days=90)
Start with services/billing.py. It changed 18 times in 90 days and has
an average complexity of 9.6, giving the highest risk score (HIGH).
models.py changes more often (31 times) but is simple code, so it
ranks lower (MEDIUM).
The most-changed file and the riskiest file are not the same. I wouldn't have guessed that either.
4. "Which button component should I use?"
My design system has 53 components. Without the server, the assistant does what it always does with an unknown component library. It invents something plausible:
> Hvilken knap-komponent skal jeg bruge?
You can use <i80-btn variant="primary">Save</i80-btn>.
That component doesn't exist. With the server, I asked with the Danish word for button, and this is the real answer:
> Hvilken knap-komponent skal jeg bruge?
⏺ get_design_component(tag="knap")
There's no component called "knap", but the closest matches are
i80-button-set, i80-button and i80-copy-button. For a single action,
use i80-button. Want its attributes and an example?
The search behind it translated Danish to English, ranked the matches and put the three button components first. Nothing got invented.
5. "What should we work on next?"
Taskz is the tool I didn't know I needed. When a job is bigger than one session, the assistant breaks it into a task board. After that, any session on any machine can ask what's next, work on it and mark it done. I have 25 boards right now, from a 63-task navigation redesign in NordBytes to the network monitoring behind my homelab's status page.
Without the server, the assistant has no idea what my backlog looks like:
> Vi skal kigge i taskz. Hvad er det næste?
I don't have access to a task system called Taskz. If you paste your
task list here, I can help you prioritise it.
With the server, this is the real answer for my i80 boards:
> Vi skal kigge i taskz. Hvad er det næste?
⏺ fetch_next_tasks(project="i80", limit=3)
Three tasks from two boards:
🟡 I80-NETWOR-005: Verify the hub, Grafana and Prometheus reachable
on uptime.i80.dk, and check that Caddy's SSL works.
🔵 I80-I80-001: Design the i80.dk logo package (SVG assets).
🔵 I80-NETWOR-007: Set up a friend's Raspberry Pi as an external
probe, with remote_write back to uptime.i80.dk.
Shall we start with the hub check?
The tasks were written weeks ago, in a different session, partly by a different assistant. They're still there, in priority order, with enough detail to start right away.
6. "Test the customer portal as Anne Marie"
NordBytes, my startup, is a work system for consultants and small businesses. Testing it as "a user" isn't enough, because a psychologist billing a municipality uses it very differently from a plumbing firm with four employees. So my knowledge catalog holds six test personas, each with a fictional company, a role, a business scenario and a list of what they may and may not do. Together they cover 23 of the 25 modules.
Without the server, "Anne Marie" is just a name:
> Test the customer portal as Anne Marie.
Who is Anne Marie? If you describe her role and what she should be
able to do, I can write a test scenario for her.
With the server, the assistant looks her up first:
> Test the customer portal as Anne Marie.
⏺ get_howto_content(filename="PERSONAS.md")
Anne Marie Petersen is Brand Manager at Nordic Fashion, a portal
customer of the marketing agency Brandlab. She can see her own
project's board, approve milestones, pay invoices and open support
tickets. She must NOT see other Brandlab customers, internal hours
or expenses.
I'll start with the magic-link login and then the most important
check: that only Nordic Fashion data shows up anywhere in the portal.
The persona turns a vague test into a precise one. The assistant knows what Anne Marie should see, and more importantly, what she must never see. The same goes for Thomas, the external auditor with read-only access to 12 tenants, and for the four other personas. The Playwright tests for each persona follow the same descriptions, so the people the assistant tests as are the same ones my automated tests use.
All six follow the same pattern. Without the server, I get advice on how to find the answer, or a confident guess. With it, I get the answer, with my rules and my context already applied.
My Data Stays Mine
Everything runs on my own hardware. Nomad schedules the container, Redis holds memories, Consul holds the secrets, and Traefik checks the token at the door. Tokens for Gitea and my calendar never reach the model. It sees a tool called gitea_list_workflow_runs, not a credential.
The side effect matters more than I expected. My memories, notes and work history aren't locked into one AI vendor's account. When a better model shows up next month, the context comes with me, because MCP is an open protocol and the data sits on my disk.
Linux box"] Copilot["Copilot
Mac"] Phone["Claude app
phone"] Josh["Josh
my own service"] end subgraph Homelab["My homelab (Nomad)"] Traefik["Traefik
token check"] Server["Personal MCP server
━━━━━━━━━━━
FastMCP
244 tools"] Memory["Memory
━━━━━━━━━━━
Redis: work context,
memories"] Knowledge["Knowledge
━━━━━━━━━━━
SQLite FTS5: howtos,
instructions, design system"] Consul["Consul KV
secrets"] end Services["Gitea + Actions
CalDAV calendar
LanguageTool, Docker"] Claude --> Traefik Copilot --> Traefik Phone --> Traefik Josh --> Traefik Traefik --> Server Server --> Memory Server --> Knowledge Server --> Services Consul -. Nomad template .-> Server
It Comes With Me
This one surprised me. Because the server speaks MCP over HTTPS with an OAuth login, the Claude app on my phone connects to it as a connector. Same tools, same memory, no extra code.
On the phone I mostly do three things:
- Calendar. "What's next today?" between meetings, or adding an appointment the moment someone mentions it.
- CI and issues. Checking whether last night's build went green, or turning an idea into a Gitea issue before I forget it.
- Notes to my future self. Saving a decision or an idea to memory. When I sit down at the desk, it's already there.
The clients don't have to be AI chat apps either. Josh, the fourth client in the diagram, is a small service of my own that talks to the same MCP server. Because the tools sit behind one protocol and one token, a new client gets calendar, Gitea and memory without a line of integration code.
Built Fast, Kept Sharp
The server started with 86 tools in January. In April, over two evenings with an AI assistant, it grew by 79 more. That's the speed AI-assisted development gives you: an idea in the afternoon is a working tool by the evening.
Speed needs a counterweight, and that part is mine. I keep an eye on overlap, because two tools that do almost the same thing only confuse the model. The clients help too: Claude Code loads a tool's full definition only when it needs it, so 244 tools don't crowd out the conversation.
Built Like Production
A personal server earns its place by being there every morning. Nine months of daily use have hardened it in a few ways I'm glad I took the time for:
Deploys without dropping a session. Nomad rolls out every new version as a canary and reverts automatically if it fails. The server speaks stateless HTTP, so a redeploy in the middle of a conversation goes unnoticed by the assistant on the other end.
Search that understands how I ask. I mix Danish and English, often in the same sentence. The search filters stop words in both languages, falls back to ranked matching when a strict search finds nothing, and matches whole words, so "pr" finds pull requests and not "prod".
Content that survives the round trip. Every document is stored exactly as written, code snippets and tables included. That sounds obvious, but it's the difference between an answer that names the right config value and one that's vaguely right.
Tests where it matters. The parts the assistant depends on most, like routing a free-text question to the right tool, have regression tests. AI writes code quickly. Tests are how I know it still does what it did yesterday.
Decide What It Must Never Do
A personal server knows a lot about you and holds tokens to a lot of your systems. My rules:
- Read-only where read is enough. Tools that write are named for what they do, like
gitea_create_issue, so I can see when one is about to run. - Dry-run by default. Cleanup tools do nothing until told otherwise.
- Secrets stay server-side. The model gets tools, never tokens.
- Anything that deletes, writes or fetches arbitrary URLs gets a second look.
Getting Started
Want to build your own? You don't need a homelab to start. A personal MCP server can begin as a single Python file on your laptop.
- FastMCP quickstart: a working server with your first tool in a few minutes.
- Build an MCP server: the official guide from the people behind the protocol.
- Connect it to Claude Code: register the server with one command, locally or over HTTP.
Then grow it in the order that gave me the most value:
- Save and resume work context.
- A memory store for decisions and preferences, reachable from your phone.
- A searchable catalog of your own instructions, shared by every assistant you use.
- Read-only tools for the systems you check every morning: CI, your Git server, your calendar.
- A monthly look at the toolbox, so it stays sharp as it grows.
Key Takeaways
- The biggest gain from a personal MCP server is context, not tools.
- Saving and resuming work context removes the "where was I?" tax at the start of every session.
- One knowledge catalog, design system included, keeps every assistant on the same page.
- With your own tools connected, the assistant gives answers instead of advice on how to find them.
- A remote server with OAuth works from the phone with no extra code.
- AI lets you go from idea to working tool in an evening; curating the toolbox keeps that speed useful.
- Treat it like production: canary deploys, stateless HTTP and regression tests make it something you can rely on every day.
An Assistant That Knows Me
After 30+ years of building systems for other people, this is one of the few I built purely for myself. It doesn't make me faster at typing code. It makes me faster at starting, because the assistant already knows the project, my calendar and what I promised to do next.
I'd build it again tomorrow. The only thing I'd change is starting sooner.
About the author: Henrik Jess is a DevOps engineer with 30+ years of infrastructure experience, specializing in Kubernetes migrations and cloud-native architecture. He's maintained self-hosted infrastructure with 99.9% uptime since 1994 and currently helps organizations bridge the gap between DevOps practices and enterprise governance requirements.