Setting Up Your First MCP Server
My first MCP server took three hours to get working because I made every possible mistake: no logging, broke stdio with print statements, forgot to restart Claude Desktop, and wondered why nothing worked. Your first one should take 30 minutes.
This is what actually works, with the debugging steps I wish I'd known upfront.
What You're Building
You're going to build a local MCP server that exposes one tool—something simple like searching your notes or creating a task. Claude Desktop will launch your server, discover the tool, and let you test it interactively.
The goal isn't production-ready infrastructure. It's a tight development loop where you can change code, restart, and see results immediately.
Prerequisites (10 Minutes)
Install Python 3.11+ (or Node.js 20+ if you prefer JavaScript):
python --version # Should be 3.11 or higher
Install FastMCP (easiest Python MCP library):
pip install fastmcp
Install Claude Desktop from claude.ai. You'll need it to test your server locally.
Find your config file location:
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json
- Windows:
%APPDATA%\Claude\claude_desktop_config.json
- Linux:
~/.config/Claude/claude_desktop_config.json
If the file doesn't exist yet, create it with {"mcpServers": {}}
.
Build Your First Server (10 Minutes)
Create a new directory and save this as server.py
:
from fastmcp import FastMCP
from pydantic import Field
from typing import Annotated
import logging
# Set up logging to stderr (important!)
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[logging.StreamHandler()]
)
logger = logging.getLogger(__name__)
mcp = FastMCP("NoteSearch")
# Fake notes database for demo
NOTES = [
{"id": "note_1", "title": "MCP Ideas", "content": "Build a demo MCP server"},
{"id": "note_2", "title": "Project Tasks", "content": "Write blog posts, review PRs"},
{"id": "note_3", "title": "Meeting Notes", "content": "Discussed MCP architecture"}
]
@mcp.tool(description="Search through your notes by keyword or phrase.")
async def search_notes(
query: Annotated[str, Field(description="Search term to find in note titles or content")]
) -> dict:
"""Search notes and return matching results."""
logger.info(f"Searching notes for: {query}")
results = [
note for note in NOTES
if query.lower() in note["title"].lower()
or query.lower() in note["content"].lower()
]
logger.info(f"Found {len(results)} matching notes")
return {
"query": query,
"count": len(results),
"results": results
}
if __name__ == "__main__":
logger.info("Starting NoteSearch MCP server...")
mcp.run()
What makes this work:
- Logging to stderr: MCP uses stdout for the protocol, so logs must go to stderr
- Simple, testable data: Hardcoded notes make testing easy
- Clear descriptions: Claude reads these to know when to use the tool
- Structured output: Returns a dict with predictable fields
Connect Claude Desktop (5 Minutes)
Edit your Claude Desktop config file:
{
"mcpServers": {
"notes": {
"command": "python",
"args": ["/full/path/to/your/server.py"],
"env": {}
}
}
}
Critical details:
- Use the full absolute path to
server.py
, not a relative path - The key
"notes"
is just a label—name it whatever you want - No trailing comma on the last item
Restart Claude Desktop completely (quit and reopen). This is the step everyone forgets.
Test It (5 Minutes)
Open Claude Desktop and start a new conversation. Ask:
"What MCP tools do you have access to?"
You should see search_notes
listed. Now try:
"Search my notes for MCP"
Claude should call your tool and show you the matching notes.
If it doesn't work:
-
Can't see the tool?
- Restart Claude Desktop (really quit, don't just close the window)
- Check the config file path is correct
- Look at Claude Desktop logs (Settings → Developer → View Logs)
-
Server crashes immediately?
- Run
python server.py
directly in terminal—you should see "Starting NoteSearch MCP server..." - If you see errors, fix them before trying Claude Desktop again
- Run
-
Tool appears but fails when called?
- Check your logs in the terminal where you ran the server
- Make sure you're not using
print()
statements (breaks stdio) - Verify JSON responses are serializable
Add Hot Reload for Development
Manually restarting gets old fast. Use watchfiles
to auto-restart on changes:
pip install watchfiles
watchfiles 'python server.py' server.py
Now when you edit server.py
, the server restarts automatically. You still need to restart Claude Desktop to pick up the changes, but the server reload is automatic.
What I Learned the Hard Way
Print statements break everything. MCP communicates over stdout. One print("debug message")
and your server stops working. Use logging
instead.
Absolute paths matter. Claude Desktop launches your server from its own working directory, so relative paths don't work. Always use full paths in the config.
Restart Claude Desktop every time. It caches server connections. Code changes won't take effect until you completely quit and restart.
Start with fake data. Don't connect to a real database on your first server. Hardcode some test data, get the basics working, then add complexity.
JSON Schema descriptions are crucial. The better your description
fields, the more accurately Claude will use your tools. "Search notes" is vague. "Search through your notes by keyword or phrase" tells Claude exactly when to use it.
Next Steps: Make It Useful
Once you have the basic server working, you can:
Add authentication by reading API keys from environment variables:
import os
API_KEY = os.environ.get("MY_API_KEY")
Connect to real data instead of hardcoded notes—database queries, API calls, file reads.
Add more tools but stay under 40 total. Beyond that, Claude starts getting confused about which tool to use.
Deploy remotely using SSE transport instead of stdio. We use mcp-remote
for this, but start with local stdio first.
Add error handling so failures return helpful messages instead of crashing.
Why This Approach Works
Starting with a simple, self-contained server teaches you the fundamentals without fighting infrastructure. You'll make mistakes with logging and stdio—everyone does—but you'll catch them immediately and fix them.
Once this works, you can build something production-ready. But get the dev loop tight first: change code, restart, test. That cycle needs to be fast, or you'll spend hours debugging instead of building.
The best MCP servers start as a single tool with fake data and evolve from there. Don't try to build everything at once.
Key Takeaways
- Start with one simple tool and fake data—get the basics working first
- Use logging, never print() because MCP uses stdout for the protocol
- Absolute paths in config files or Claude Desktop won't find your server
- Restart Claude Desktop completely after every config or server change
- Hot reload your server with
watchfiles
to speed up development - JSON Schema descriptions matter more than you think—Claude reads them to decide when to use tools
Resources
- FastMCP GitHub – Best Python MCP library for getting started
- Building Your First MCP Server – Alternative tutorial with different approaches
- .NET MCP Quickstart – If you prefer C# over Python
- Anthropic MCP Docs – Official MCP documentation
Got it working? Have questions? We're all learning this together—reach out.