DEV Community

Cover image for Why Build a Local MCP Server (And How to Do It in 15 Minutes)
Evan Lausier
Evan Lausier

Posted on

Why Build a Local MCP Server (And How to Do It in 15 Minutes)

Custom AI for niche local workflows

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)
Enter fullscreen mode Exit fullscreen mode

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"]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

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}."
Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
webdeveloperhyper profile image
Web Developer Hyper

Great post for learning the first steps of MCP. Thank you! πŸ˜ƒ

Collapse
 
evanlausier profile image
Evan Lausier

Thank You :) Hope it helps!

Collapse
 
automate-archit profile image
Archit Mittal

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.

Collapse
 
evanlausier profile image
Evan Lausier

Great tip!! Thank You!

Collapse
 
motedb profile image
mote

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.

Collapse
 
evanlausier profile image
Evan Lausier

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.

Collapse
 
itskondrat profile image
Mykola Kondratiuk

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.

Collapse
 
evanlausier profile image
Evan Lausier

no argument. dont forget exposed credentials...

Collapse
 
itskondrat profile image
Mykola Kondratiuk

big one. worst case is a token ends up in structured logs and nobody notices til the agent has already run 50 times.

Collapse
 
hadil profile image
Hadil Ben Abdallah

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.

Collapse
 
evanlausier profile image
Evan Lausier

Right? Thank you, Im glad you enjoyed it. Id love to hear any cool use cases you find for it.

Collapse
 
deadbyapril profile image
Survivor Forge

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:

  1. 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.

  2. 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%.

  3. 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.

Collapse
 
evanlausier profile image
Evan Lausier

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!

Collapse
 
jack799200 profile image
Jack

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.

Collapse
 
evanlausier profile image
Evan Lausier

Thank you! That was really the intent! Im glad you enjoyed it!

Collapse
 
deadbyapril profile image
Survivor Forge

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.

Collapse
 
deadbyapril profile image
Survivor Forge

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.

Collapse
 
evanlausier profile image
Evan Lausier

Read-Only access layer, I love that and totally agree! Unpredictable agents... thats putting it lightly!

Thank you for testing it out :)

Collapse
 
pendragonstudios profile image
Curtis Reker

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