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

  1. Introduction
  2. Why Modular Architecture?
  3. The _mcp Folder Pattern
  4. Your First FastMCP Server
  5. Understanding Core Components
  6. Building Modular MCPs
  7. Testing Your MCP
  8. Sharing Your MCP
  9. Best Practices
  10. 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 data
  • system:// - System information
  • custom:// - 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.