Reading about MCP is one thing; building a working server is another. This walks through the minimum real version: a Python MCP server exposing one useful tool, run locally, and connected to an MCP-compatible AI application. No prior MCP experience assumed — if you haven’t read the conceptual overview first, start there for the host/client/server model this builds on.
What You’ll Need
- Python 3.10 or newer
- The official MCP Python SDK (
pip install mcp) - An MCP-compatible client to test with (Claude Desktop, or an editor with MCP support)
Step 1: Define the Server and a Tool
An MCP server’s core job is exposing tools an AI host can call. Here’s a minimal server exposing one tool — checking word count of a piece of text:
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("text-tools")
@mcp.tool()
def word_count(text: str) -> int:
"""Count the number of words in a piece of text."""
return len(text.split())
if __name__ == "__main__":
mcp.run()
The @mcp.tool() decorator does the real work: it registers word_count as a callable tool, using the function’s docstring as the description the AI host sees when deciding whether to call it. This is the whole pattern — every additional tool is just another decorated function.
Step 2: Add a Second, More Realistic Tool
A single-tool server proves the concept but isn’t useful yet. A more realistic example — reading a local file’s contents, scoped to one safe directory:
import os
ALLOWED_DIR = os.path.expanduser("~/mcp-files")
@mcp.tool()
def read_file(filename: str) -> str:
"""Read the contents of a file from the allowed directory."""
path = os.path.join(ALLOWED_DIR, filename)
if not os.path.abspath(path).startswith(os.path.abspath(ALLOWED_DIR)):
raise ValueError("Access outside the allowed directory is not permitted.")
with open(path, "r", encoding="utf-8") as f:
return f.read()
Notice the path check before opening anything — this matters more than it looks like it does. An AI host will call your tool with whatever arguments the model decides to pass, so a server that trusts input blindly (allowing ../../ style paths, for example) is a real vulnerability, not a hypothetical one. Scope every tool’s file/database/API access as narrowly as the task actually requires.
Step 3: Connect It to an MCP Host
Most MCP hosts (Claude Desktop included) read a config file listing which MCP servers to launch and connect to. A typical entry looks like this:
{
"mcpServers": {
"text-tools": {
"command": "python",
"args": ["/full/path/to/your_server.py"]
}
}
}
Restart the host application after adding this, and it should discover both tools automatically — no separate registration step. Ask it something that requires the tool (“how many words are in this paragraph?”) and it should call word_count on its own.
Step 4: Test It Actually Works
Don’t just trust that it’s wired up correctly — ask the host a question that specifically requires the tool, not something it could plausibly guess without calling it, and confirm the returned value is actually correct (not just plausible-sounding). For the file-reading tool, put a file with known, distinctive content in the allowed directory and ask the host to summarize it — if the summary reflects the file’s real content, the tool call genuinely happened.
Frequently Asked Questions
Does an MCP server need to be written in Python?
No — official SDKs exist for Python, TypeScript, and other languages. The protocol itself is language-agnostic; pick whichever SDK matches your existing stack.
Can one MCP server expose more than one tool?
Yes, and it usually should — group related tools (like several file or database operations) into one server rather than a separate server per tool, unless there’s a real reason to isolate them (different permission levels, for example).
Is it safe to connect an AI host to a server with real file or API access?
Only with real scoping and guardrails — least-privilege access, path/input validation like the example above, and human approval for anything destructive or irreversible. Treat any input your tool receives as untrusted, since it’s the model’s interpretation of a user request, not a verified command.
Conclusion
The whole pattern is smaller than it looks from the outside: decorate a function, describe what it does, scope its access narrowly, and any MCP-compatible host can use it. The part that actually takes care is the scoping and validation, not the protocol plumbing — that’s exactly where a server should not be trusted blindly, which the next guide in this series covers in more depth.
📑 About the author: I also build Digital Bizz Card — hosted digital business cards you can share with a QR code, no app required.


