Tutorial FastMCP Quickstart Guide - 80-20-Human-In-The-Loop/Community GitHub Wiki
FastMCP Quickstart Guide
Learn to build modular, reusable MCP servers that integrate seamlessly with the 80-20 ecosystem
Table of Contents
- Introduction
- Why Modular Architecture?
- The _mcp Folder Pattern
- Your First FastMCP Server
- Understanding Core Components
- Building Modular MCPs
- Testing Your MCP
- Sharing Your MCP
- Best Practices
- Next Steps
Introduction
FastMCP is the Pythonic way to build MCP (Model Context Protocol) servers. It transforms simple Python functions into powerful tools that AI agents like Claude Code, Cursor, and Gemini can use.
What Makes FastMCP Special?
- Simple: Decorate functions with
@mcp.tool
- that's it! - Modular: Build reusable components that work together
- Community-First: Designed for sharing and collaboration
- Production-Ready: Built-in testing, validation, and error handling
Who This Guide Is For
- Beginners: New to MCP? We'll start from the basics
- Tool Builders: Want to create tools for the community
- Teams: Need standardized AI integrations
Why Modular Architecture?
The Problem with Monolithic MCPs
Imagine building everything in one giant file:
# โ Bad: Everything in one place
# my_giant_mcp.py (1000+ lines)
def tool1(): ...
def tool2(): ...
def tool3(): ...
# ... 50 more tools
This becomes:
- Hard to maintain
- Difficult to test
- Impossible to share parts
- Nightmare to debug
The Modular Solution
The 80-20 community uses a modular pattern:
project/
โโโ _mcp/ # Your MCP lives here
โโโ main.py # The coordinator
โโโ servers/ # Specialized modules
โ โโโ __init__.py
โ โโโ code_tools.py # Code-related tools
โ โโโ data_tools.py # Data-related tools
โ โโโ test_tools.py # Testing tools
โโโ tests/ # Test each module
Benefits:
- Clear Organization: Each file has a purpose
- Easy Testing: Test modules independently
- Reusable: Share modules via PyPI
- Maintainable: Teams can work on different parts
The _mcp Folder Pattern
Why "_mcp"?
The underscore prefix (_mcp
) is a community convention that means:
- Internal Tool: This MCP serves this project
- Not Core Code: Separate from your main application
- Standardized Location: Everyone knows where to look
Standard Structure
Every MCP in our ecosystem follows this pattern:
your_project/
โโโ src/ # Your main project code
โโโ tests/ # Your project tests
โโโ docs/ # Your documentation
โโโ _mcp/ # ๐ฏ Your MCP server
โโโ main.py # Entry point & coordinator
โโโ servers/ # Modular components
โ โโโ __init__.py
โ โโโ core.py # Essential tools
โ โโโ advanced.py # Specialized tools
โโโ resources/ # Static resources
โโโ prompts/ # Reusable prompts
โโโ tests/ # MCP tests
The main.py Coordinator
The main.py
file is special - it's the conductor of your orchestra:
# _mcp/main.py
from fastmcp import FastMCP
# Import modular servers
from servers.core import core_server
from servers.advanced import advanced_server
# Create the main server
mcp = FastMCP("My Project MCP")
# Mount modular servers (like adding USB devices!)
mcp.mount(core_server) # Add core tools
mcp.mount(advanced_server) # Add advanced tools
# Global configuration
@mcp.resource("project://info")
def project_info():
return {"name": "My Project", "version": "1.0.0"}
if __name__ == "__main__":
mcp.run()
Your First FastMCP Server
Let's build a real MCP from scratch. We'll create a "Study Assistant MCP" that helps with learning.
Step 1: Create the Structure
# Create your project
mkdir study-assistant
cd study-assistant
# Create the MCP structure
mkdir -p _mcp/servers _mcp/tests
touch _mcp/main.py
touch _mcp/servers/__init__.py
touch _mcp/servers/core.py
Step 2: Build the Core Module
Start with a simple, focused module:
# _mcp/servers/core.py
"""
Core study tools - the essentials for learning
"""
from fastmcp import FastMCP
# Create a modular server
core_server = FastMCP("Study Core Tools")
@core_server.tool
def explain_concept(concept: str, level: str = "beginner") -> str:
"""
Explain a concept at different levels
Args:
concept: The concept to explain
level: "beginner", "intermediate", or "advanced"
"""
explanations = {
"mcp": {
"beginner": "MCP is like USB for AI - it lets AI tools connect to your code",
"intermediate": "MCP standardizes how LLMs interact with external tools and data",
"advanced": "MCP provides a protocol for bidirectional communication between LLMs and computational resources"
}
}
concept_lower = concept.lower()
if concept_lower in explanations:
return explanations[concept_lower].get(level, explanations[concept_lower]["beginner"])
return f"Let me explain {concept} at a {level} level: [Would need real implementation]"
@core_server.tool
def create_flashcard(term: str, definition: str) -> dict:
"""
Create a study flashcard
Args:
term: The term to learn
definition: Its definition
Returns:
A formatted flashcard
"""
return {
"type": "flashcard",
"front": term,
"back": definition,
"created": "just now",
"difficulty": "new"
}
@core_server.resource("study://progress")
def get_study_progress():
"""Track learning progress"""
return {
"concepts_learned": 5,
"flashcards_created": 12,
"study_time_today": "45 minutes"
}
Step 3: Create the Main Coordinator
# _mcp/main.py
"""
Study Assistant MCP - Modular learning tools
"""
from fastmcp import FastMCP
from servers.core import core_server
# Create the main server
mcp = FastMCP(
name="Study Assistant MCP",
instructions="""
๐ **Study Assistant MCP**
I help you learn more effectively with:
- Concept explanations at multiple levels
- Flashcard creation and management
- Progress tracking
Built with modular architecture for easy extension!
"""
)
# Mount the core module
mcp.mount(core_server)
# Add a global tool
@mcp.tool
def study_status() -> str:
"""Get your current study status"""
return """
๐ Study Status:
- Mode: Active Learning
- Focus: High
- Next: Review flashcards in 10 minutes
"""
if __name__ == "__main__":
mcp.run()
Step 4: Test Your MCP
Create a simple test to verify everything works:
# _mcp/test_mcp.py
"""Test our Study Assistant MCP"""
import asyncio
from fastmcp import Client
async def test_study_mcp():
# Connect to our MCP
client = Client("_mcp/main.py")
async with client:
print("๐งช Testing Study Assistant MCP\n")
# Test concept explanation
result = await client.call_tool(
"explain_concept",
{"concept": "MCP", "level": "beginner"}
)
print(f"โ
Explanation: {result}\n")
# Test flashcard creation
flashcard = await client.call_tool(
"create_flashcard",
{"term": "FastMCP", "definition": "Python framework for building MCP servers"}
)
print(f"โ
Flashcard created: {flashcard}\n")
# Test study status
status = await client.call_tool("study_status")
print(f"โ
Status: {status}")
if __name__ == "__main__":
asyncio.run(test_study_mcp())
Run the test:
# Terminal 1: Start the server
python _mcp/main.py
# Terminal 2: Run the test
python _mcp/test_mcp.py
Understanding Core Components
Tools: Actions Your MCP Can Take
Tools are functions that DO things:
@mcp.tool
def send_email(to: str, subject: str, body: str) -> dict:
"""Send an email (with user permission)"""
# Implementation here
return {"status": "sent", "to": to}
Key Points:
- Always include docstrings
- Use type hints for clarity
- Return structured data
- Handle errors gracefully
Resources: Data Your MCP Provides
Resources are read-only data sources:
@mcp.resource("project://stats")
def get_project_stats():
"""Provide project statistics"""
return {
"files": 42,
"lines_of_code": 1337,
"last_updated": "today"
}
URI Patterns:
project://
- Project-specific datasystem://
- System informationcustom://
- Your own patterns
Prompts: Reusable Templates
Prompts ensure consistent AI behavior:
@mcp.prompt
def code_review_prompt(code: str) -> str:
"""Standard code review template"""
return f"""
Review this code for:
1. Logic errors
2. Performance issues
3. Security concerns
Code:
{code}
"""
Building Modular MCPs
Creating Specialized Modules
Each module should focus on one area:
# _mcp/servers/testing.py
"""Testing tools module"""
from fastmcp import FastMCP
testing_server = FastMCP("Testing Tools")
@testing_server.tool
def run_tests(test_file: str = None) -> dict:
"""Run project tests"""
# Implementation
return {"passed": 10, "failed": 0}
@testing_server.tool
def generate_test(function_name: str) -> str:
"""Generate a test for a function"""
return f"def test_{function_name}(): ..."
Composing Modules in main.py
# _mcp/main.py
from fastmcp import FastMCP
from servers.core import core_server
from servers.testing import testing_server
from servers.docs import docs_server
# Create main server
mcp = FastMCP("Project MCP")
# Mount all modules
mcp.mount(core_server)
mcp.mount(testing_server)
mcp.mount(docs_server)
# The main server coordinates everything!
Conditional Module Loading
Load modules based on environment:
import os
mcp = FastMCP("Adaptive MCP")
# Always load core
mcp.mount(core_server)
# Load testing only in development
if os.getenv("ENVIRONMENT") != "production":
from servers.testing import testing_server
mcp.mount(testing_server)
# Load advanced features for power users
if os.getenv("POWER_USER") == "true":
from servers.advanced import advanced_server
mcp.mount(advanced_server)
Testing Your MCP
Unit Testing Individual Modules
Test each module separately:
# _mcp/tests/test_core.py
import pytest
from servers.core import core_server
def test_explain_concept():
"""Test concept explanation"""
# Get the actual function
explain = core_server.get_tool("explain_concept").function
result = explain("MCP", "beginner")
assert "USB for AI" in result
result = explain("MCP", "advanced")
assert "protocol" in result
Integration Testing
Test how modules work together:
# _mcp/tests/test_integration.py
import asyncio
from fastmcp import Client
async def test_full_mcp():
client = Client("_mcp/main.py")
async with client:
# Test tools from different modules
core_result = await client.call_tool("explain_concept", {"concept": "testing"})
test_result = await client.call_tool("run_tests")
assert core_result is not None
assert test_result["passed"] >= 0
Validation Helpers
Create validation scripts:
# _mcp/validate.py
"""Validate MCP server before deployment"""
def check_imports():
"""Ensure all imports work"""
try:
from main import mcp
from servers.core import core_server
print("โ
All imports successful")
return True
except ImportError as e:
print(f"โ Import error: {e}")
return False
def check_tools():
"""Verify tools are registered"""
from main import mcp
tools = mcp.list_tools()
print(f"โ
Found {len(tools)} tools")
for tool in tools:
print(f" - {tool.name}")
return len(tools) > 0
if __name__ == "__main__":
if check_imports() and check_tools():
print("\n๐ MCP validation passed!")
else:
print("\nโ MCP validation failed")
Sharing Your MCP
Preparing for PyPI Distribution
Structure your MCP as a Python package:
stormchecker-mcp/
โโโ pyproject.toml
โโโ README.md
โโโ src/
โ โโโ stormchecker_mcp/
โ โโโ __init__.py
โ โโโ main.py
โ โโโ servers/
โ โโโ __init__.py
โ โโโ core.py
โโโ tests/
The init.py File
Make your MCP importable:
# src/stormchecker_mcp/__init__.py
"""StormChecker MCP - Type checking tools for the community"""
from .main import mcp as stormchecker_mcp
from .servers.core import core_server
__version__ = "1.0.0"
__all__ = ["stormchecker_mcp", "core_server"]
Using Community MCPs
Once published, others can use your MCP:
# Someone else's _mcp/main.py
from fastmcp import FastMCP
from stormchecker_mcp import stormchecker_mcp
from django_mercury_mcp import mercury_mcp
# Create their main MCP
mcp = FastMCP("My Project MCP")
# Add community MCPs!
mcp.mount(stormchecker_mcp) # Add Storm Checker tools
mcp.mount(mercury_mcp) # Add Django Mercury tools
# Add their own tools
@mcp.tool
def my_custom_tool():
"""Project-specific tool"""
return "Custom functionality"
Best Practices
1. Error Handling
Always handle errors gracefully:
@mcp.tool
def safe_file_read(path: str) -> str:
"""Safely read a file with proper error handling"""
try:
with open(path, 'r') as f:
return f.read()
except FileNotFoundError:
return f"โ File not found: {path}"
except PermissionError:
return f"โ Permission denied: {path}"
except Exception as e:
return f"โ Unexpected error: {str(e)}"
2. Documentation
Every tool needs clear documentation:
@mcp.tool
def analyze_code(
file_path: str,
include_metrics: bool = True,
style_check: bool = False
) -> dict:
"""
Analyze code quality and provide recommendations
Args:
file_path: Path to the file to analyze
include_metrics: Include complexity metrics (default: True)
style_check: Run style checking (default: False)
Returns:
Dictionary with analysis results including:
- issues: List of found issues
- metrics: Code metrics (if requested)
- suggestions: Improvement suggestions
Example:
result = analyze_code("main.py", style_check=True)
print(f"Found {len(result['issues'])} issues")
"""
# Implementation
3. Security
Follow the 80-20 philosophy - AI handles 80%, humans control 20%:
@mcp.tool
def delete_file(path: str, confirm: bool = False) -> str:
"""
Delete a file (requires explicit confirmation)
Args:
path: File to delete
confirm: Must be True to actually delete
"""
if not confirm:
return "โ ๏ธ Set confirm=True to delete files"
# Additional safety check
if any(danger in path for danger in ["/", "..", "~"]):
return "โ Unsafe path detected"
# Actual deletion
os.remove(path)
return f"โ
Deleted: {path}"
4. Modularity
Keep modules focused and single-purpose:
# โ
Good: Focused module
# servers/formatting.py
formatting_server = FastMCP("Formatting Tools")
@formatting_server.tool
def format_python(code: str) -> str:
"""Format Python code"""
@formatting_server.tool
def format_json(data: str) -> str:
"""Format JSON data"""
# โ Bad: Kitchen sink module
# servers/everything.py
everything_server = FastMCP("Everything")
@everything_server.tool
def format_code(): ...
@everything_server.tool
def send_email(): ...
@everything_server.tool
def query_database(): ...
# Too many unrelated tools!
5. Testing
Test early, test often:
# _mcp/tests/conftest.py
import pytest
from fastmcp import Client
@pytest.fixture
async def mcp_client():
"""Fixture for MCP client"""
client = Client("_mcp/main.py")
async with client:
yield client
# _mcp/tests/test_tools.py
async def test_tool_execution(mcp_client):
"""Test that tools execute correctly"""
result = await mcp_client.call_tool(
"explain_concept",
{"concept": "testing", "level": "beginner"}
)
assert result is not None
assert "testing" in result.lower()
Next Steps
1. Build Your First MCP
Start with the template:
# Clone the starter template
git clone https://github.com/80-20-Human-In-The-Loop/mcp-starter
cd mcp-starter
# Install dependencies
pip install fastmcp
# Start building!
python _mcp/main.py
2. Learn Advanced Patterns
- Server Composition: Combine multiple MCPs
- Dynamic Loading: Load modules based on context
- Custom Transports: Beyond stdio
- Authentication: Secure your MCPs
- Observability: Monitor MCP usage
3. Join the Community
- Share your MCPs on PyPI
- Contribute to existing MCPs
- Join discussions about best practices
- Help others learn
4. Resources
Summary
You've learned:
- โ Why modular architecture matters
- โ
The
_mcp
folder pattern - โ How to build FastMCP servers
- โ Creating reusable modules
- โ Testing and validation
- โ Sharing MCPs with the community
Remember the 80-20 philosophy:
- 80% Automation: Let MCPs handle repetitive tasks
- 20% Human Control: Keep critical decisions with humans
Now go build something amazing! ๐
Part of the 80-20 Human in The Loop ecosystem - Building tools that make developers smarter, not obsolete.