# 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 1. [Introduction](#introduction) 2. [Prerequisites](#prerequisites) 3. [The _mcp Folder Pattern](#the-_mcp-folder-pattern) 4. [Step 1: Create Your Project Structure](#step-1-create-your-project-structure) 5. [Step 2: Your First Tool](#step-2-your-first-tool) 6. [Step 3: Add a Resource](#step-3-add-a-resource) 7. [Step 4: Create a Prompt](#step-4-create-a-prompt) 8. [Step 5: Verify It Works](#step-5-verify-it-works) 9. [Step 6: Make It Modular](#step-6-make-it-modular) 10. [Step 7: Run Your MCP](#step-7-run-your-mcp) 11. [Understanding What You Built](#understanding-what-you-built) 12. [Next Steps](#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: ```bash pip install fastmcp ``` Verify installation: ```bash fastmcp version ``` --- ## The _mcp Folder Pattern Remember from the [FastMCP Quickstart Guide](Tutorial-FastMCP-Quickstart-Guide.md): 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: ```bash # 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`: ```python """ 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`: ```python 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: ```bash # 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`: ```python @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`: ```python @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`: ```python """ 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: ```bash 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|Tutorial-MCP-Testing]] 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](Tutorial-FastMCP-Quickstart-Guide.md), let's split into modules. Create `_mcp/servers/journal.py`: ```python """ 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: ```python """ 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 ```bash python _mcp/main.py ``` ### Method 2: FastMCP CLI ```bash # 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`: ```python 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 1. **Tools** (`record_learning`): ACTIONS that modify state - The AI can help you record learnings - You decide what's worth recording 2. **Resources** (`journal://recent`, `journal://stats`): READ-ONLY data - The AI can read your journal - Provides context for conversations 3. **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 everything - `servers/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: ```python # 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: ```json // 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: ```python # 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 ```python # Test each component separately async def test_tools(): ... async def test_resources(): ... async def test_prompts(): ... ``` ### Handle Errors Gracefully ```python @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 ```python @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?](Tutorial-What-Is-An-MCP-Server.md) - [FastMCP Quickstart Guide](Tutorial-FastMCP-Quickstart-Guide.md) - [How To Write 80-20 Tools](Tutorial-How-To-Write-80-20-Tools.md) - [FastMCP Documentation](https://gofastmcp.com) - [Community Discussions](https://github.com/orgs/80-20-Human-In-The-Loop/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)|Tutorial-MCP-Testing]] 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.*