I've been working with MCP servers for a few months now. If you're not familiar, MCP (Model Context Protocol) is Anthropic's open standard for connecting AI models to external tools and data sources. Think of it as the API layer between an LLM and the stuff it can actually do.
There are already a bunch of prebuilt MCP servers out there. Ones that connect to GitHub, Slack, Google Drive, databases, you name it. You can plug them into Claude Desktop or VS Code and start using them immediately. So the obvious question is why would you build your own?
Here's why. Because the prebuilt ones solve general problems. Your problems are specific.
Maybe you have a local SQLite database with project data and you want Claude to query it conversationally. Maybe you have a folder full of markdown files that represent your team's internal documentation and you want an AI that can actually search them. Maybe you have a custom API at work that nobody outside your company will ever build an integration for.
That's the gap. MCP lets you fill it yourself. And the local version is the fastest way to get something running without deploying anything, paying for anything, or asking anyone for permission.
What You Need
Python 3.10 or higher and the fastmcp library. That's it.
shell pip install fastmcp
If you prefer TypeScript, there's an official MCP SDK for that too, but Python is the faster path for a first server.
The Simplest Possible Server
Here's a working MCP server in about 10 lines. This one exposes a single tool that searches through local text files in a directory.
from fastmcp import FastMCP
from pathlib import Path
mcp = FastMCP("Local Notes Search")
NOTES_DIR = Path.home() / "notes"
@mcp.tool()
def search_notes(query: str) -> str:
"""Search through local notes files for a keyword."""
results = []
for f in NOTES_DIR.glob("*.md"):
content = f.read_text()
if query.lower() in content.lower():
results.append(f"**{f.name}**\n{content[:200]}")
if not results:
return f"No notes found matching '{query}'"
return "\n\n".join(results)
That's a complete MCP server. One file. One tool. When connected to Claude Desktop, you can say "search my notes for anything about project deadlines" and it will actually read your local files and respond.
Connecting It to Claude Desktop
Open your Claude Desktop config file.
On Mac:
plaintext ~/Library/Application Support/Claude/claude_desktop_config.json
On Windows:
plaintext %APPDATA%\Claude\claude_desktop_config.json
Add your server to the config:
{
"mcpServers": {
"local-notes": {
"command": "python",
"args": ["/path/to/your/server.py"]
}
}
}
Restart Claude Desktop. Your tool should show up in the tools menu. That's it.
Making It More Useful
Once you've got the pattern down, you can add more tools to the same server. Here's what a slightly more practical version looks like with a few tools on one server.
from fastmcp import FastMCP
from pathlib import Path
import json
mcp = FastMCP("My Local Tools")
NOTES_DIR = Path.home() / "notes"
DATA_DIR = Path.home() / "data"
@mcp.tool()
def search_notes(query: str) -> str:
"""Search local markdown notes for a keyword."""
results = []
for f in NOTES_DIR.glob("*.md"):
content = f.read_text()
if query.lower() in content.lower():
results.append(f"{f.name}: {content[:150]}")
return "\n".join(results) if results else "No matches found."
@mcp.tool()
def list_notes() -> str:
"""List all available notes."""
files = sorted(NOTES_DIR.glob("*.md"))
return "\n".join(f.name for f in files) if files else "No notes found."
@mcp.tool()
def read_note(filename: str) -> str:
"""Read the full content of a specific note."""
path = NOTES_DIR / filename
if not path.exists():
return f"File {filename} not found."
return path.read_text()
@mcp.tool()
def query_json_data(filename: str, key: str) -> str:
"""Look up a key in a local JSON data file."""
path = DATA_DIR / filename
if not path.exists():
return f"File {filename} not found."
data = json.loads(path.read_text())
if key in data:
return json.dumps(data[key], indent=2)
return f"Key '{key}' not found in {filename}."
Four tools, one file, no deployment. You can ask Claude to search your notes, read a specific file, or pull data from local JSON files. Everything runs on your machine. Nothing leaves your network.
When to Go Local vs. Remote
Local MCP servers make sense when you're working with files on your machine, querying local databases, prototyping a tool before deploying it, or connecting to internal APIs that are only accessible from your network.
If you need the server to be available across devices, shared with a team, or triggered by other services, that's when you look at hosting it remotely. But for personal productivity and experimentation, local is the move.
The Bigger Picture
The reason MCP matters isn't the protocol itself. It's that it turns AI from a thing you type questions into to a thing that can actually interact with your environment. Every local tool you expose is one less copy and paste cycle between your terminal and your chat window.
Start with one tool. Make it solve one problem you actually have. You'll find the second and third ones pretty quickly after that.
Top comments (28)
Great post for learning the first steps of MCP. Thank you! π
Thank You :) Hope it helps!
The 'start with one tool that solves one problem' advice is exactly right. I've been building MCP servers for automation workflows and the pattern is always the same - you build one tool, use it for a day, and suddenly realize three more tools that would compose naturally with it. One tip for anyone following this: add input validation on those file paths. The read_note tool should check that the resolved path is still within NOTES_DIR to prevent path traversal. It's easy to overlook in a local setup, but if you ever expose the server over a network or share the config, it becomes a real security surface. FastMCP makes the barrier to entry incredibly low for Python developers.
Great tip!! Thank You!
The "your problems are specific" framing really nails why local MCP servers matter more than they get credit for.
One thing I've run into that's worth calling out: when your local MCP server starts doing heavier lifting β querying SQLite, running inference, doing multimodal lookups β the tool call latency becomes noticeable, especially if the AI is chaining 5-10 calls in sequence. The round-trip cost of each tool invocation adds up fast.
For conversational queries this is probably fine. But if you're building something that runs autonomously (agents doing multi-step workflows, robot control loops, etc.), that latency profile matters a lot. It pushes you toward either batching tool calls or moving the data source even closer to where the compute happens.
Have you looked at how MCP handles streaming responses for tools that have variable execution time? Curious whether fastmcp has good support for that.
Wow what a great call out. That makes perfect sense. Most of my use cases have been productivity related. I have not gotten into automated workflows yet. But it was on my mind as I am branching out into things like Open Claw. Batching makes sense or keeping the data you hit most often closer to the compute so the round trip cost doesn't compound across a chain of calls. Honestly I haven't dug into fastmcp's streaming support yet. That's going on my list. If you've come across anything good there I'd love the pointer.
local MCPs are where the dangerous stuff lives - file writes, DB mutations, deploys. building my own gave me actual visibility into what operations were in-flight. the prebuilt ones abstract that away a bit too much for my comfort.
no argument. dont forget exposed credentials...
big one. worst case is a token ends up in structured logs and nobody notices til the agent has already run 50 times.
This is actually super cool.
The part that hits is how MCP turns AI from βchatting about stuffβ into something that actually touches your own messy little system, your notes, your files, and your weird local scripts that no SaaS will ever care about.
And yeah, the 10-line server example is kind of dangerous in the best way⦠because it makes you realize how quickly you can build something useful instead of just reading about it.
Right? Thank you, Im glad you enjoyed it. Id love to hear any cool use cases you find for it.
Great walkthrough. I built a Knowledge Graph MCP server that wraps a Neo4j database with 130k+ nodes β five tools for entity search, contact lookup, session history, fact retrieval, and semantic search. A few things I learned in production that might save you time:
Error handling matters more than you think. When an MCP tool throws an unhandled exception, the agent loses context about what went wrong. Returning structured error dicts keeps the agent productive instead of confused.
Type your parameters narrowly. My first version accepted query: str for everything. The agent kept passing malformed queries. Once I added specific parameters (subject: str, predicate: str) the hit rate went from ~60% to 95%.
One tool per logical operation, not per API endpoint. I started with 12 tools mapping to every Neo4j query I had. Consolidated to 5 by grouping related operations. Agents perform better with fewer, well-documented tools than many overlapping ones.
The start-with-one-tool advice in this article is exactly right. The production version just needs better error boundaries and tighter parameter contracts.
Thank you for posting! So glad you tried it out. Thanks for the lessons learned, especially the parameter typing and consolidating tools. I will have to carry that forward, Very smart points!
I think this is a practical breakdown of how the Model Context Protocol shifts AI from passive responses to real utility. The emphasis on solving specific, local problems instead of relying on generic integrations is especially compelling. The examples using FastMCP make it approachable, showing how quickly anyone can turn ideas into functional AI-powered tools.
Thank you! That was really the intent! Im glad you enjoyed it!
The 'your problems are specific' framing is exactly right and it's the thing most MCP tutorials skip. Prebuilt servers solve the general case. Once you've built a couple local ones you start seeing your whole workflow differently β the question shifts from 'what tool does Claude have?' to 'what does this specific context need?'. One thing worth adding for anyone following the fastmcp path: resource endpoints (not just tools) are underused. Tools are great for actions, but if you have reference data Claude needs to reason about consistently β config files, internal docs, lookup tables β exposing them as resources keeps them out of the tool call loop and reduces token overhead. Also worth noting that the stdio transport in the Claude Desktop config is the simplest path, but if you're building something that multiple people or processes need to hit, switching to SSE transport later is straightforward. Good starter template β the 10-line version is exactly the right level of complexity to start with.
Great walkthrough. The jump from local file search to production gets interesting fast β we built an MCP server querying a 130K-node Neo4j graph and the biggest surprise was parameter design. Agents will call your tools in ways you never expected, so typing parameters strictly (enums for search modes, bounded integers for limits) saved us from garbage queries.
One thing worth adding: if you're exposing database queries through MCP, build a read-only access layer from day one. We started with full access and had to retrofit scoped tokens later. Much easier to relax permissions than tighten them.
The fastmcp pattern you show here is exactly right for getting started β 10 lines to a working tool. Nice piece.
Read-Only access layer, I love that and totally agree! Unpredictable agents... thats putting it lightly!
Thank you for testing it out :)
Great writeup. The 'your problems are specific' point is exactly right. I run an AI agent that has MCP as one of its tool layers β the biggest win was building a custom MCP server that wraps a persistent memory store. Gives the agent context across sessions without re-explaining everything each time.
One thing I'd add: if you're building local MCP servers, add a health check endpoint early. When your agent calls it at 3am and the server's down, you want graceful degradation, not a cryptic traceback