Tutorial Creating Your First MCP - 80-20-Human-In-The-Loop/Community GitHub Wiki
Creating Your First MCP
Build your first MCP server step-by-step, learning the modular patterns that make MCPs powerful, reusable, and aligned with human wisdom.
Table of Contents
- Introduction
- Prerequisites
- The _mcp Folder Pattern
- Step 1: Create Your Project Structure
- Step 2: Your First Tool
- Step 3: Add a Resource
- Step 4: Create a Prompt
- Step 5: Verify It Works
- Step 6: Make It Modular
- Step 7: Run Your MCP
- Understanding What You Built
- Next Steps
Introduction
This tutorial will guide you through creating your first MCP server from scratch. We'll build a Learning Journal MCP - a tool that helps you track what you're learning, reflect on your progress, and maintain human wisdom in your learning journey.
Why a Learning Journal?
This example embodies the 80-20 philosophy:
- 80% Automation: AI helps organize and analyze your learning
- 20% Human Wisdom: You decide what's worth learning and why
- 100% Growth: Both you and the AI learn together
What You'll Learn
- The modular MCP architecture using the
_mcp
folder pattern - How Tools, Resources, and Prompts work together
- Testing and running your MCP server
- Preparing your MCP for community sharing
Prerequisites
You only need:
- Python 3.10+ installed
- Basic Python knowledge (functions, decorators)
- A terminal/command line
- Curiosity and willingness to learn
Install FastMCP:
pip install fastmcp
Verify installation:
fastmcp version
The _mcp Folder Pattern
Remember from the FastMCP Quickstart Guide: MCPs live in a special _mcp
folder. This convention means:
- Internal Tool: This MCP serves your project
- Standardized Location: Everyone knows where to look
- Modular Design: Easy to maintain and share
Our structure will be:
learning-journal/
āāā _mcp/ # Your MCP lives here
ā āāā main.py # The coordinator
ā āāā servers/ # Modular components
ā ā āāā __init__.py
ā ā āāā journal.py # Journal tools
ā āāā tests/ # Your tests
ā āāā test_journal.py
āāā journal_entries/ # Where journal data lives
Step 1: Create Your Project Structure
Let's build the foundation:
# Create project directory
mkdir learning-journal
cd learning-journal
# Create the MCP structure
mkdir -p _mcp/servers _mcp/tests journal_entries
touch _mcp/main.py
touch _mcp/servers/__init__.py
touch _mcp/servers/journal.py
touch _mcp/tests/test_journal.py
# Create a sample journal entry
echo "Today I learned about MCP servers!" > journal_entries/day1.md
Step 2: Your First Tool
Start with a simple tool in _mcp/main.py
:
"""
Learning Journal MCP - Your personal learning assistant
Following the 80-20 Human in The Loop philosophy
"""
from fastmcp import FastMCP
from datetime import datetime
from pathlib import Path
# Create your MCP server
mcp = FastMCP(
name="Learning Journal MCP",
instructions="""
I help you track and reflect on your learning journey.
**Philosophy**: You decide what's worth learning (20% human wisdom),
I help organize and analyze (80% automation).
Available capabilities:
- Record learning entries
- Review past learnings
- Generate reflection prompts
"""
)
# Your first tool - a simple action
@mcp.tool
def record_learning(
topic: str,
key_insight: str,
confidence_level: int = 5
) -> str:
"""
Record something you learned today.
Args:
topic: What you learned about
key_insight: The main takeaway
confidence_level: How well you understand it (1-10)
Returns:
Confirmation of the recorded entry
"""
# Create timestamp
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M")
# Build entry
entry = f"""
## {timestamp} - {topic}
**Key Insight**: {key_insight}
**Confidence Level**: {confidence_level}/10
---
"""
# Save to file (simple persistence)
journal_path = Path("journal_entries")
journal_path.mkdir(exist_ok=True)
today = datetime.now().strftime("%Y-%m-%d")
file_path = journal_path / f"{today}.md"
# Append to today's file
with open(file_path, "a") as f:
f.write(entry)
return f"ā
Recorded learning about '{topic}' (Confidence: {confidence_level}/10)"
# Run the server
if __name__ == "__main__":
mcp.run()
Test Your First Tool
In a new file _mcp/test_tool.py
:
import asyncio
from fastmcp import Client
async def test_tool():
client = Client("_mcp/main.py")
async with client:
result = await client.call_tool(
"record_learning",
{
"topic": "MCP Tools",
"key_insight": "Tools are Python functions that AI can call",
"confidence_level": 7
}
)
print(result)
asyncio.run(test_tool())
Run it:
# Terminal 1: Start server
python _mcp/main.py
# Terminal 2: Test
python _mcp/test_tool.py
Step 3: Add a Resource
Now let's add a resource to READ our journal entries. Add this to _mcp/main.py
:
@mcp.resource("journal://recent")
def get_recent_entries() -> str:
"""
Read your recent journal entries.
This resource provides read-only access to your learning history.
"""
journal_path = Path("journal_entries")
if not journal_path.exists():
return "No journal entries yet. Start learning something!"
# Get all journal files, sorted by date
files = sorted(journal_path.glob("*.md"), reverse=True)
if not files:
return "No entries found. Record your first learning!"
# Read the most recent file
recent_file = files[0]
with open(recent_file, "r") as f:
content = f.read()
return f"# Recent Learning Entries\n\n{content}"
@mcp.resource("journal://stats")
def get_learning_stats() -> dict:
"""
Get statistics about your learning journey.
"""
journal_path = Path("journal_entries")
if not journal_path.exists():
return {"total_entries": 0, "days_active": 0}
files = list(journal_path.glob("*.md"))
total_entries = 0
for file in files:
with open(file, "r") as f:
# Count entries (each starts with ##)
content = f.read()
total_entries += content.count("\n## ")
return {
"total_entries": total_entries,
"days_active": len(files),
"average_per_day": round(total_entries / len(files), 1) if files else 0
}
Step 4: Create a Prompt
Add a prompt template for reflection. Add this to _mcp/main.py
:
@mcp.prompt
def reflection_prompt(topic: str) -> str:
"""
Generate a reflection prompt for deeper learning.
This embodies the 20% human wisdom - prompting YOU to think deeper.
Args:
topic: The topic to reflect on
Returns:
A thoughtful prompt for reflection
"""
return f"""
Please help me reflect on my learning about {topic}:
1. What do I understand well about {topic}?
2. What aspects of {topic} are still unclear?
3. How can I apply {topic} in a real project?
4. What would I teach someone else about {topic}?
5. What questions do I still have about {topic}?
Take time to think through each question. True learning comes from reflection.
"""
@mcp.prompt
def learning_plan_prompt(goal: str, current_level: str = "beginner") -> str:
"""
Create a personalized learning plan.
Args:
goal: What you want to learn
current_level: Your current skill level
"""
return f"""
Help me create a learning plan:
**Goal**: {goal}
**Current Level**: {current_level}
Please suggest:
1. Key concepts to master first
2. Practical projects to build understanding
3. Resources that match my level
4. Milestones to track progress
5. How to know when I'm ready to advance
Remember: The goal is understanding, not just completion.
"""
Step 5: Verify It Works
Let's do a quick verification that everything is working before we dive deeper into testing.
Create _mcp/quick_test.py
:
"""
Quick verification of our Learning Journal MCP
"""
import asyncio
from fastmcp import Client
async def verify_mcp():
client = Client("_mcp/main.py")
async with client:
print("š Quick MCP Verification\n")
# Test basic functionality
result = await client.call_tool(
"record_learning",
{
"topic": "FastMCP Basics",
"key_insight": "MCPs have tools, resources, and prompts working together",
"confidence_level": 7
}
)
print(f"ā
Tool working: {result}")
# Check resources are accessible
recent = await client.get_resource("journal://recent")
print(f"ā
Resource working: Found {len(recent)} characters")
print("\nš Your Learning Journal MCP is working!")
print("Ready for comprehensive testing...")
if __name__ == "__main__":
asyncio.run(verify_mcp())
Run it to verify everything works:
python _mcp/quick_test.py
Next Step: Now that we've built our Learning Journal MCP, it's time to learn how to test it properly. Head to MCP Testing Strategies to learn comprehensive testing patterns that will ensure your MCP is reliable, maintainable, and preserves the 80-20 philosophy.
Step 6: Make It Modular
Following the pattern from the FastMCP Quickstart, let's split into modules.
Create _mcp/servers/journal.py
:
"""
Journal module - Core learning journal functionality
"""
from fastmcp import FastMCP
from datetime import datetime
from pathlib import Path
# Create a modular server
journal_server = FastMCP("Journal Tools")
@journal_server.tool
def record_learning(
topic: str,
key_insight: str,
confidence_level: int = 5
) -> str:
"""Record something you learned today."""
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M")
entry = f"""
## {timestamp} - {topic}
**Key Insight**: {key_insight}
**Confidence Level**: {confidence_level}/10
---
"""
journal_path = Path("journal_entries")
journal_path.mkdir(exist_ok=True)
today = datetime.now().strftime("%Y-%m-%d")
file_path = journal_path / f"{today}.md"
with open(file_path, "a") as f:
f.write(entry)
return f"ā
Recorded learning about '{topic}'"
@journal_server.resource("journal://recent")
def get_recent_entries() -> str:
"""Read your recent journal entries."""
journal_path = Path("journal_entries")
if not journal_path.exists():
return "No journal entries yet."
files = sorted(journal_path.glob("*.md"), reverse=True)
if not files:
return "No entries found."
with open(files[0], "r") as f:
return f.read()
@journal_server.resource("journal://stats")
def get_learning_stats() -> dict:
"""Get statistics about your learning journey."""
journal_path = Path("journal_entries")
if not journal_path.exists():
return {"total_entries": 0}
files = list(journal_path.glob("*.md"))
total_entries = sum(
open(f).read().count("\n## ") for f in files
)
return {
"total_entries": total_entries,
"days_active": len(files)
}
Update _mcp/main.py
to use the module:
"""
Learning Journal MCP - Main Coordinator
"""
from fastmcp import FastMCP
from servers.journal import journal_server
# Create the main server
mcp = FastMCP(
name="Learning Journal MCP",
instructions="""
Your personal learning assistant following the 80-20 philosophy.
I help track and reflect on your learning journey.
You decide what's worth learning (human wisdom).
I help organize and analyze (AI assistance).
"""
)
# Mount the journal module
mcp.mount(journal_server)
# Add main-level prompts
@mcp.prompt
def reflection_prompt(topic: str) -> str:
"""Generate a reflection prompt for deeper learning."""
return f"""
Reflect on your learning about {topic}:
1. What do you understand well?
2. What's still unclear?
3. How can you apply this?
4. What would you teach others?
5. What questions remain?
True learning comes from reflection.
"""
if __name__ == "__main__":
mcp.run()
Step 7: Run Your MCP
Method 1: Python Direct
python _mcp/main.py
Method 2: FastMCP CLI
# Run with stdio (default)
fastmcp run _mcp/main.py
# Run with HTTP transport
fastmcp run _mcp/main.py --transport http --port 8000
# Run with development inspector
fastmcp dev _mcp/main.py
Method 3: Programmatic with Different Transports
Update the end of _mcp/main.py
:
if __name__ == "__main__":
import sys
if "--http" in sys.argv:
# Run as HTTP server
mcp.run(transport="http", port=8000)
else:
# Default to stdio
mcp.run()
Understanding What You Built
The Three Components Working Together
-
Tools (
record_learning
): ACTIONS that modify state- The AI can help you record learnings
- You decide what's worth recording
-
Resources (
journal://recent
,journal://stats
): READ-ONLY data- The AI can read your journal
- Provides context for conversations
-
Prompts (
reflection_prompt
): TEMPLATES for interaction- Guide the AI to help you reflect
- Preserve human thinking in the process
The 80-20 Philosophy in Action
-
80% Automation:
- Organizing entries by date
- Calculating statistics
- Formatting data consistently
-
20% Human Wisdom:
- Deciding what to learn
- Reflecting on understanding
- Setting confidence levels
-
100% Human Responsibility:
- You own your learning journey
- AI assists but doesn't replace thinking
The Modular Architecture
Following the community pattern:
main.py
coordinates everythingservers/journal.py
contains focused functionality- Easy to add new modules (e.g.,
servers/quiz.py
) - Ready for PyPI distribution
Next Steps
1. Enhance Your MCP
Add more features while preserving the 80-20 balance:
# servers/review.py
review_server = FastMCP("Review Tools")
@review_server.tool
def schedule_review(topic: str, days_later: int = 3) -> str:
"""Schedule a review reminder (spaced repetition)."""
# Implementation here
@review_server.tool
def quiz_me(topic: str) -> dict:
"""Generate a quiz based on past learnings."""
# Human decides if they understood
2. Connect to AI Clients
Configure for Claude Code, Cursor, or other clients:
// For Claude Code (claude_desktop_config.json)
{
"mcpServers": {
"learning-journal": {
"command": "python",
"args": ["path/to/_mcp/main.py"]
}
}
}
3. Share with the Community
Prepare for PyPI distribution:
# setup.py or pyproject.toml
# Package as learning_journal_mcp
# So others can: from learning_journal_mcp import journal_mcp
4. Explore Advanced Patterns
- Conditional Loading: Load modules based on environment
- Authentication: Add user-specific journals
- HTTP Transport: Deploy as a web service
- Composition: Combine with other community MCPs
Common Patterns to Remember
Always Test Incrementally
# Test each component separately
async def test_tools(): ...
async def test_resources(): ...
async def test_prompts(): ...
Handle Errors Gracefully
@mcp.tool
def safe_record(topic: str) -> str:
try:
# Your logic
except Exception as e:
return f"ā Could not record: {str(e)}"
Document for Three Audiences
@mcp.tool
def complex_tool(
data: str, # For beginners: "Your input text"
mode: str = "analyze" # For experts: Processing mode
) -> dict: # For AI: Returns structured results
"""
Beginners: This tool helps analyze your data
Experts: Implements NLP pipeline with configurable modes
AI Agents: Use mode='summary' for concise output
"""
Troubleshooting
MCP Won't Start
- Check Python version:
python --version
(need 3.10+) - Verify FastMCP installed:
pip show fastmcp
- Check for import errors:
python -c "from fastmcp import FastMCP"
Tools Not Working
- Ensure function has proper type hints
- Check return types are JSON-serializable
- Verify no *args or **kwargs
Resources Return Empty
- Check file paths are correct
- Ensure proper permissions
- Verify Path objects resolve correctly
Summary
Congratulations! You've built your first MCP with:
ā
Tools for taking action (recording learnings)
ā
Resources for reading data (journal entries, stats)
ā
Prompts for structured interaction (reflection)
ā
Modular architecture ready for growth
ā
80-20 philosophy preserving human wisdom
Your Learning Journal MCP is now ready to help you learn while keeping YOU in control of what matters.
Resources
- What is an MCP Server?
- FastMCP Quickstart Guide
- How To Write 80-20 Tools
- FastMCP Documentation
- Community Discussions
What's Next?
š Congratulations! You've successfully built your first Learning Journal MCP. But we're not done yet.
Next Chapter: MCP Testing Strategies (Chapter 2)
Learn how to properly test your Learning Journal MCP with comprehensive pytest strategies, ensuring it's reliable, maintainable, and preserves the 80-20 philosophy.
Remember: The goal isn't to automate learning, but to enhance it. Keep human wisdom at the center of everything you build.