Project 1: MCP Protocol Explorer

Project 1: MCP Protocol Explorer

Learn the foundation of ChatGPT Apps by building and testing MCP tools


Quick Reference

Attribute Details
Difficulty Level 1: Beginner
Time Estimate Weekend (8-12 hours)
Primary Language Python (FastMCP)
Alternative Languages TypeScript/Node.js (MCP SDK)
Prerequisites Basic Python, understanding of REST APIs, JSON
Key Topics MCP Protocol, Tool Design, JSON Schema, Request/Response Cycle
Tools Required FastMCP, MCP Inspector, Python 3.9+, uvicorn

Learning Objectives

By completing this project, you will:

  1. Understand MCP fundamentals: Explain what the Model Context Protocol is, why it exists, and how it enables AI models to interact with external systems
  2. Design effective tools: Create tool definitions with clear names, descriptions, and parameter schemas that guide the modelโ€™s behavior
  3. Implement JSON Schema validation: Define parameter types, constraints, and optional fields using JSON Schema patterns
  4. Test MCP servers: Use MCP Inspector to test tool invocations, examine request/response cycles, and debug issues
  5. Handle errors gracefully: Implement error handling that provides useful feedback to both the model and users
  6. Structure tool outputs: Design tool responses that are both human-readable and machine-parsable
  7. Apply tool design principles: Follow best practices for naming, descriptions, annotations, and single responsibility

Theoretical Foundation

What is the Model Context Protocol (MCP)?

The Model Context Protocol (MCP) is an open standard that allows AI models to connect to external tools, data sources, and services. Think of it as โ€œUSB-C for AI applicationsโ€โ€”a universal connector that works across different AI platforms.

Why does MCP exist?

  1. Limited Knowledge: AI models like ChatGPT have a knowledge cutoff and canโ€™t access real-time data
  2. No Actions: Models can only generate text; they canโ€™t create calendar events, send emails, or update databases
  3. Platform Lock-in: Before MCP, each AI platform had its own proprietary integration format
  4. Standardization: MCP provides a common language for AI-tool integration

The Core Architecture:

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                    MCP ARCHITECTURE                         โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚                                                             โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                      โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
โ”‚  โ”‚              โ”‚   MCP Protocol       โ”‚                โ”‚  โ”‚
โ”‚  โ”‚   ChatGPT    โ”‚โ—„โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บโ”‚   MCP Server   โ”‚  โ”‚
โ”‚  โ”‚   (Client)   โ”‚   (JSON-RPC 2.0)     โ”‚   (Your Code)  โ”‚  โ”‚
โ”‚  โ”‚              โ”‚                      โ”‚                โ”‚  โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                      โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
โ”‚         โ”‚                                      โ”‚            โ”‚
โ”‚         โ”‚                                      โ”‚            โ”‚
โ”‚    Decides which                        Executes tools     โ”‚
โ”‚    tool to call                         and returns data   โ”‚
โ”‚    based on user                                           โ”‚
โ”‚    input and tool                                          โ”‚
โ”‚    descriptions                                            โ”‚
โ”‚                                                             โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

MCP Core Primitives

MCP defines three main primitives (though this project focuses on Tools):

  1. Tools: Functions the model can execute
    • Example: get_weather(city), search_products(query)
    • Tools perform actions or retrieve data
    • Each tool has a name, description, and parameter schema
  2. Resources: Structured documents the model can read
    • Example: user://profile, company://settings
    • Resources provide context and knowledge
    • Often used for user-specific data
  3. Prompts: Templates or instructions for the model
    • Example: โ€œWhen user asks about orders, always include statusโ€
    • Prompts guide model behavior
    • Used for consistent response patterns

How Tools Work in MCP

The Tool Lifecycle:

1. DISCOVERY: ChatGPT asks your server "What tools do you have?"
   โ†“
   Your server responds with a list of tool definitions

2. SELECTION: User asks "What's the weather in Tokyo?"
   โ†“
   ChatGPT analyzes tool descriptions and selects get_weather

3. INVOCATION: ChatGPT calls get_weather with parameters
   โ†“
   Request: { "name": "get_weather", "arguments": { "city": "Tokyo" } }

4. EXECUTION: Your server executes the tool
   โ†“
   You call a weather API, fetch data, format response

5. RESPONSE: Your server returns structured data
   โ†“
   Response: { "temperature": 15, "condition": "Cloudy" }

6. PRESENTATION: ChatGPT uses the data in its response
   โ†“
   "The current weather in Tokyo is 15ยฐC and cloudy."

Tool Definition Structure:

{
  "name": "get_weather",
  "description": "Use this when user asks about weather conditions for a city.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "city": {
        "type": "string",
        "description": "The city name"
      },
      "units": {
        "type": "string",
        "enum": ["celsius", "fahrenheit"],
        "description": "Temperature units"
      }
    },
    "required": ["city"]
  }
}

JSON Schema for Parameter Validation

JSON Schema defines what parameters your tool accepts. Itโ€™s crucial for:

  • Type safety: Ensures ChatGPT sends the right data types
  • Validation: Catches invalid inputs before execution
  • Documentation: Helps the model understand parameter constraints

Common JSON Schema Patterns:

# String with constraints
{
  "type": "string",
  "minLength": 1,
  "maxLength": 100,
  "pattern": "^[A-Za-z]+$"  # Only letters
}

# Number with range
{
  "type": "number",
  "minimum": 0,
  "maximum": 100,
  "exclusiveMinimum": true  # Must be > 0, not >= 0
}

# Enum (restricted choices)
{
  "type": "string",
  "enum": ["red", "green", "blue"]
}

# Array of items
{
  "type": "array",
  "items": {
    "type": "string"
  },
  "minItems": 1,
  "maxItems": 10
}

# Object with nested properties
{
  "type": "object",
  "properties": {
    "name": { "type": "string" },
    "age": { "type": "integer" }
  },
  "required": ["name"]
}

# Optional parameter (not in required array)
{
  "type": "string",
  "default": "default_value"
}

The Request/Response Cycle

What happens when ChatGPT calls your tool?

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                 REQUEST/RESPONSE CYCLE                      โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚                                                             โ”‚
โ”‚  USER: "What's the weather in Paris?"                       โ”‚
โ”‚    โ”‚                                                        โ”‚
โ”‚    โ–ผ                                                        โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
โ”‚  โ”‚ ChatGPT analyzes:                                     โ”‚  โ”‚
โ”‚  โ”‚ - User intent: wants weather information             โ”‚  โ”‚
โ”‚  โ”‚ - Available tools: get_weather matches              โ”‚  โ”‚
โ”‚  โ”‚ - Parameters needed: city="Paris"                    โ”‚  โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
โ”‚    โ”‚                                                        โ”‚
โ”‚    โ–ผ                                                        โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
โ”‚  โ”‚ MCP Request (JSON-RPC 2.0):                          โ”‚  โ”‚
โ”‚  โ”‚ {                                                     โ”‚  โ”‚
โ”‚  โ”‚   "jsonrpc": "2.0",                                   โ”‚  โ”‚
โ”‚  โ”‚   "method": "tools/call",                             โ”‚  โ”‚
โ”‚  โ”‚   "params": {                                         โ”‚  โ”‚
โ”‚  โ”‚     "name": "get_weather",                            โ”‚  โ”‚
โ”‚  โ”‚     "arguments": {                                    โ”‚  โ”‚
โ”‚  โ”‚       "city": "Paris",                                โ”‚  โ”‚
โ”‚  โ”‚       "units": "celsius"                              โ”‚  โ”‚
โ”‚  โ”‚     }                                                 โ”‚  โ”‚
โ”‚  โ”‚   },                                                  โ”‚  โ”‚
โ”‚  โ”‚   "id": 1                                             โ”‚  โ”‚
โ”‚  โ”‚ }                                                     โ”‚  โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
โ”‚    โ”‚                                                        โ”‚
โ”‚    โ–ผ                                                        โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
โ”‚  โ”‚ Your MCP Server:                                     โ”‚  โ”‚
โ”‚  โ”‚ 1. Receives request                                  โ”‚  โ”‚
โ”‚  โ”‚ 2. Validates parameters against schema               โ”‚  โ”‚
โ”‚  โ”‚ 3. Executes get_weather function                     โ”‚  โ”‚
โ”‚  โ”‚ 4. Calls external weather API                        โ”‚  โ”‚
โ”‚  โ”‚ 5. Formats response data                             โ”‚  โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
โ”‚    โ”‚                                                        โ”‚
โ”‚    โ–ผ                                                        โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
โ”‚  โ”‚ MCP Response:                                        โ”‚  โ”‚
โ”‚  โ”‚ {                                                     โ”‚  โ”‚
โ”‚  โ”‚   "jsonrpc": "2.0",                                   โ”‚  โ”‚
โ”‚  โ”‚   "result": {                                         โ”‚  โ”‚
โ”‚  โ”‚     "content": [{                                     โ”‚  โ”‚
โ”‚  โ”‚       "type": "text",                                 โ”‚  โ”‚
โ”‚  โ”‚       "text": "{                                      โ”‚  โ”‚
โ”‚  โ”‚         \"temperature\": 12,                          โ”‚  โ”‚
โ”‚  โ”‚         \"condition\": \"Rainy\",                     โ”‚  โ”‚
โ”‚  โ”‚         \"humidity\": 85,                             โ”‚  โ”‚
โ”‚  โ”‚         \"wind\": \"15 km/h W\"                       โ”‚  โ”‚
โ”‚  โ”‚       }"                                              โ”‚  โ”‚
โ”‚  โ”‚     }]                                                โ”‚  โ”‚
โ”‚  โ”‚   },                                                  โ”‚  โ”‚
โ”‚  โ”‚   "id": 1                                             โ”‚  โ”‚
โ”‚  โ”‚ }                                                     โ”‚  โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
โ”‚    โ”‚                                                        โ”‚
โ”‚    โ–ผ                                                        โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
โ”‚  โ”‚ ChatGPT processes response:                          โ”‚  โ”‚
โ”‚  โ”‚ - Parses the structured data                         โ”‚  โ”‚
โ”‚  โ”‚ - Generates natural language response                โ”‚  โ”‚
โ”‚  โ”‚ - Presents to user                                   โ”‚  โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
โ”‚    โ”‚                                                        โ”‚
โ”‚    โ–ผ                                                        โ”‚
โ”‚  ChatGPT: "The current weather in Paris is 12ยฐC and rainy, โ”‚
โ”‚            with 85% humidity and winds at 15 km/h from      โ”‚
โ”‚            the west."                                       โ”‚
โ”‚                                                             โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Tool Design Best Practices

1. Single Responsibility Principle

  • Each tool should do ONE thing well
  • โœ… Good: get_order_status, cancel_order, update_shipping
  • โŒ Bad: manage_order (too broad, unclear what it does)

2. Clear, Descriptive Naming

  • Use verb phrases that describe the action
  • โœ… Good: search_products, add_to_cart, create_ticket
  • โŒ Bad: products, cart, ticket (not clear these are actions)

3. Effective Descriptions

  • Start with โ€œUse this whenโ€ฆโ€
  • Describe the user intent, not implementation details
  • โœ… Good: โ€œUse this when user wants to find products by category or nameโ€
  • โŒ Bad: โ€œProduct search functionโ€ (not helpful for routing)

4. Structured Outputs

  • Return data thatโ€™s both human-readable and machine-parsable
  • Include relevant IDs, timestamps, status codes
  • โœ… Good: {"orderId": "12345", "status": "shipped", "eta": "2025-12-25"}
  • โŒ Bad: "Your order is on the way!" (not reusable by other tools)

5. Tool Annotations

  • readOnlyHint: true - Safe operations with no side effects
  • destructiveHint: true - Deletes or modifies data (requires confirmation)
@mcp.tool(annotations={"readOnlyHint": True})
def get_weather(city: str) -> dict:
    """Safe read operation"""
    pass

@mcp.tool(annotations={"destructiveHint": True})
def delete_account(user_id: str) -> dict:
    """Dangerous operation - ChatGPT will ask for confirmation"""
    pass

Common Misconceptions

Misconception #1: โ€œMCP is just another REST APIโ€

  • Reality: MCP is a higher-level protocol built on JSON-RPC 2.0. It includes discovery, schemas, and semantic tool descriptions that help AI models understand capabilities.

Misconception #2: โ€œTool descriptions donโ€™t matter muchโ€

  • Reality: Descriptions are CRITICAL. Theyโ€™re how ChatGPT decides which tool to call. A poor description means your tool wonโ€™t be selected, even if itโ€™s perfect for the task.

Misconception #3: โ€œI should combine multiple actions into one toolโ€

  • Reality: Keep tools focused. ChatGPT can chain multiple tool calls together. Single-purpose tools are more reliable and reusable.

Misconception #4: โ€œJSON Schema is just documentationโ€

  • Reality: JSON Schema provides runtime validation. It prevents errors and helps the model understand constraints like min/max values, required fields, and allowed values.

Misconception #5: โ€œMCP servers are complex microservicesโ€

  • Reality: An MCP server can be as simple as a single Python file with a few functions. FastMCP handles all the protocol details for you.

Project Specification

What Youโ€™ll Build

A simple MCP server that exposes 3-5 tools demonstrating different patterns:

  1. Information Retrieval Tool: Fetches external data (e.g., weather, stock prices)
  2. Calculation/Conversion Tool: Performs computations (e.g., currency conversion, unit conversion)
  3. Parameterized Search Tool: Accepts multiple parameters with different types
  4. Tool with Optional Parameters: Demonstrates default values and optional fields
  5. Error Handling Tool (bonus): Shows how to handle and report errors gracefully

Requirements

Functional Requirements:

  • Server starts and responds to MCP Inspector
  • At least 3 tools are defined with clear names and descriptions
  • Each tool has a properly defined JSON Schema for parameters
  • Tools return structured data (JSON objects)
  • At least one tool calls an external API (or simulates it)
  • At least one tool has optional parameters with defaults
  • Error cases are handled gracefully

Technical Requirements:

  • Python 3.9+ with FastMCP
  • Server runs on localhost:8000
  • Tools follow naming conventions (verb phrases)
  • Descriptions start with โ€œUse this whenโ€ฆโ€
  • Outputs include relevant metadata (timestamps, IDs, status)

Testing Requirements:

  • All tools can be invoked via MCP Inspector
  • Valid inputs return expected outputs
  • Invalid inputs return clear error messages
  • Edge cases are handled (empty strings, out-of-range values)

Example Output

When you complete this project, you should be able to:

# Start your server
$ python mcp_server.py

FastMCP server running on http://localhost:8000
Registered 4 tools:
  - get_weather
  - convert_currency
  - get_stock_price
  - calculate_distance

Then in MCP Inspector:

# Connect to http://localhost:8000
# See list of tools

> invoke get_weather {"city": "London", "units": "celsius"}

{
  "city": "London",
  "temperature": 8,
  "condition": "Cloudy",
  "humidity": 78,
  "wind": "20 km/h SW",
  "timestamp": "2025-12-26T10:30:00Z"
}

> invoke convert_currency {"amount": 100, "from": "USD", "to": "EUR"}

{
  "amount": 100,
  "from": "USD",
  "to": "EUR",
  "result": 92.45,
  "rate": 0.9245,
  "timestamp": "2025-12-26T10:31:00Z"
}

> invoke calculate_distance {"from": "New York", "to": "Los Angeles"}

{
  "from": "New York",
  "to": "Los Angeles",
  "distance_km": 3944,
  "distance_miles": 2451,
  "duration_hours": 5.5
}

Solution Architecture

High-Level Design

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                 MCP SERVER ARCHITECTURE                     โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚                                                             โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”   โ”‚
โ”‚  โ”‚              FastMCP Application                    โ”‚   โ”‚
โ”‚  โ”‚  (Handles MCP protocol, JSON-RPC, discovery)        โ”‚   โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜   โ”‚
โ”‚                         โ”‚                                   โ”‚
โ”‚                         โ”‚                                   โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”          โ”‚
โ”‚  โ”‚              โ”‚                โ”‚              โ”‚          โ”‚
โ”‚  โ–ผ              โ–ผ                โ–ผ              โ–ผ          โ”‚
โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”         โ”‚
โ”‚ โ”‚ Tool 1 โ”‚  โ”‚ Tool 2 โ”‚  โ”‚  Tool 3  โ”‚  โ”‚ Tool 4  โ”‚         โ”‚
โ”‚ โ”‚Weather โ”‚  โ”‚Currencyโ”‚  โ”‚  Stock   โ”‚  โ”‚Distance โ”‚         โ”‚
โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜         โ”‚
โ”‚      โ”‚           โ”‚             โ”‚             โ”‚             โ”‚
โ”‚      โ”‚           โ”‚             โ”‚             โ”‚             โ”‚
โ”‚      โ–ผ           โ–ผ             โ–ผ             โ–ผ             โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”        โ”‚
โ”‚  โ”‚Weather โ”‚  โ”‚Exchangeโ”‚  โ”‚  Stock   โ”‚  โ”‚Geocodingโ”‚        โ”‚
โ”‚  โ”‚  API   โ”‚  โ”‚Rate APIโ”‚  โ”‚   API    โ”‚  โ”‚   API   โ”‚        โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜        โ”‚
โ”‚                                                             โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Core Components

1. FastMCP Application

  • Entry point for the server
  • Handles all MCP protocol communication
  • Automatically generates tool schemas from Python type hints
  • Manages tool discovery and invocation

2. Tool Functions

  • Python functions decorated with @mcp.tool()
  • Each function = one MCP tool
  • Type hints โ†’ JSON Schema
  • Docstrings โ†’ tool descriptions

3. External API Integration

  • Tools can call external services (weather, finance, etc.)
  • For this project, you can use:
    • Real APIs (with API keys)
    • Mock/stub implementations (simulated data)
    • Both approaches are valid for learning

4. Error Handling Layer

  • Validates inputs against schemas
  • Catches and reports API errors
  • Returns structured error responses

Data Structures

Tool Definition (automatically generated by FastMCP):

{
  "name": "get_weather",
  "description": "Use this when user asks about weather conditions",
  "inputSchema": {
    "type": "object",
    "properties": {
      "city": {"type": "string"},
      "units": {"type": "string", "enum": ["celsius", "fahrenheit"]}
    },
    "required": ["city"]
  }
}

Tool Response Structure:

{
  # Core data
  "temperature": 15,
  "condition": "Cloudy",

  # Metadata (always useful)
  "city": "Tokyo",
  "timestamp": "2025-12-26T10:30:00Z",

  # Optional enrichment
  "units": "celsius",
  "source": "OpenWeatherMap"
}

How FastMCP Works

FastMCP is a Python library that simplifies MCP server development:

from fastmcp import FastMCP

# Create MCP application
mcp = FastMCP("My MCP Server")

# Define tools using decorators
@mcp.tool()
def my_tool(param1: str, param2: int = 10) -> dict:
    """Tool description here."""
    # Your logic
    return {"result": "data"}

# FastMCP automatically:
# 1. Extracts function signature โ†’ JSON Schema
# 2. Uses docstring โ†’ tool description
# 3. Handles JSON-RPC protocol
# 4. Validates inputs
# 5. Routes requests to your function

Why FastMCP is great for learning:

  • No boilerplate code for protocol handling
  • Type hints become schemas automatically
  • Focus on tool logic, not protocol details
  • Built-in error handling

Implementation Guide

Phase 0: Setup & Environment

Step 1: Install Dependencies

# Create project directory
mkdir mcp-protocol-explorer
cd mcp-protocol-explorer

# Create virtual environment
python3 -m venv venv
source venv/bin/activate  # On Windows: venv\Scripts\activate

# Install FastMCP and dependencies
pip install fastmcp uvicorn httpx python-dotenv

# Install MCP Inspector (for testing)
npm install -g @modelcontextprotocol/inspector

Step 2: Create Project Structure

mcp-protocol-explorer/
โ”œโ”€โ”€ mcp_server.py          # Your MCP server
โ”œโ”€โ”€ requirements.txt       # Dependencies
โ”œโ”€โ”€ .env                   # API keys (optional)
โ”œโ”€โ”€ README.md             # Documentation
โ””โ”€โ”€ tests/                # Tests (optional)
    โ””โ”€โ”€ test_tools.py

Step 3: Verify Installation

# Check FastMCP is installed
python -c "import fastmcp; print(fastmcp.__version__)"

# Check MCP Inspector is available
mcp-inspector --version

Checkpoint: You should have a clean project directory with FastMCP installed.


Phase 1: Build Your First Tool

Objective: Create a simple MCP server with one working tool.

Task 1.1: Create the basic server

Create mcp_server.py:

from fastmcp import FastMCP
import uvicorn

# Create MCP application
mcp = FastMCP("MCP Protocol Explorer")

# Define your first tool
@mcp.tool()
def get_weather(city: str, units: str = "celsius") -> dict:
    """Use this when user asks about weather conditions for a specific city.

    Args:
        city: The name of the city to get weather for
        units: Temperature units - either celsius or fahrenheit
    """
    # For now, return mock data
    # You'll connect to a real API later
    return {
        "city": city,
        "temperature": 18 if units == "celsius" else 64,
        "condition": "Partly Cloudy",
        "humidity": 65,
        "wind": "12 km/h NW",
        "units": units,
        "timestamp": "2025-12-26T10:30:00Z"
    }

# Run the server
if __name__ == "__main__":
    # FastMCP creates an ASGI app
    uvicorn.run(mcp.get_asgi_app(), host="0.0.0.0", port=8000)

Task 1.2: Start the server

python mcp_server.py

You should see:

INFO:     Started server process
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://0.0.0.0:8000

Task 1.3: Test with MCP Inspector

# In a new terminal
npx @modelcontextprotocol/inspector
  1. Open the URL shown (usually http://localhost:5173)
  2. Enter your server URL: http://localhost:8000
  3. Click โ€œConnectโ€
  4. You should see your get_weather tool listed
  5. Click it to view the schema
  6. Test it with: {"city": "Paris", "units": "celsius"}

Checkpoint: Your tool appears in MCP Inspector and returns data when invoked.


Phase 2: Add More Tools

Objective: Add 2-3 more tools demonstrating different patterns.

Task 2.1: Currency Conversion Tool

Add this to mcp_server.py:

@mcp.tool()
def convert_currency(
    amount: float,
    from_currency: str,
    to_currency: str
) -> dict:
    """Use this when user wants to convert money between different currencies.

    Args:
        amount: The amount of money to convert
        from_currency: The source currency code (e.g., USD, EUR, GBP)
        to_currency: The target currency code (e.g., USD, EUR, GBP)
    """
    # Mock exchange rates (you'll add real API later)
    exchange_rates = {
        "USD": 1.0,
        "EUR": 0.92,
        "GBP": 0.79,
        "JPY": 149.5,
        "CAD": 1.35
    }

    # Calculate conversion
    # Convert from_currency to USD, then to to_currency
    usd_amount = amount / exchange_rates.get(from_currency, 1.0)
    result = usd_amount * exchange_rates.get(to_currency, 1.0)

    return {
        "amount": amount,
        "from": from_currency,
        "to": to_currency,
        "result": round(result, 2),
        "rate": round(exchange_rates.get(to_currency, 1.0) /
                     exchange_rates.get(from_currency, 1.0), 4),
        "timestamp": "2025-12-26T10:30:00Z"
    }

Task 2.2: Stock Price Tool

@mcp.tool()
def get_stock_price(symbol: str) -> dict:
    """Use this when user asks about stock prices or market data for a company.

    Args:
        symbol: The stock ticker symbol (e.g., AAPL, GOOGL, MSFT)
    """
    # Mock stock data
    mock_prices = {
        "AAPL": 178.25,
        "GOOGL": 142.50,
        "MSFT": 375.80,
        "TSLA": 245.60,
        "AMZN": 155.30
    }

    price = mock_prices.get(symbol.upper(), 100.00)

    return {
        "symbol": symbol.upper(),
        "price": price,
        "currency": "USD",
        "change": round((price * 0.02), 2),  # Mock 2% change
        "change_percent": 2.0,
        "timestamp": "2025-12-26T10:30:00Z",
        "market_status": "open"
    }

Task 2.3: Test all tools

Restart your server and test each tool in MCP Inspector:

# Test currency conversion
{"amount": 100, "from_currency": "USD", "to_currency": "EUR"}

# Test stock price
{"symbol": "AAPL"}

Checkpoint: You have 3 working tools, each with different parameter patterns.


Phase 3: Add JSON Schema Constraints

Objective: Improve parameter validation with schema constraints.

Task 3.1: Add enum constraint to currency tool

Update convert_currency:

from typing import Literal

@mcp.tool()
def convert_currency(
    amount: float,
    from_currency: Literal["USD", "EUR", "GBP", "JPY", "CAD"],
    to_currency: Literal["USD", "EUR", "GBP", "JPY", "CAD"]
) -> dict:
    """Use this when user wants to convert money between different currencies.

    Args:
        amount: The amount of money to convert (must be positive)
        from_currency: The source currency code
        to_currency: The target currency code
    """
    # Validation
    if amount <= 0:
        return {
            "error": "Amount must be positive",
            "amount": amount
        }

    # Rest of implementation...

Task 3.2: Add a tool with complex parameters

Add a new tool that calculates distance between cities:

from typing import Optional

@mcp.tool()
def calculate_distance(
    from_city: str,
    to_city: str,
    unit: Literal["km", "miles"] = "km"
) -> dict:
    """Use this when user wants to know the distance between two cities.

    Args:
        from_city: The starting city name
        to_city: The destination city name
        unit: The unit for distance (km or miles)
    """
    # Mock distance calculation
    # In reality, you'd use geocoding + distance formula
    mock_distances = {
        ("New York", "Los Angeles"): 3944,
        ("London", "Paris"): 344,
        ("Tokyo", "Osaka"): 400,
    }

    # Try to find distance (order-independent)
    key1 = (from_city, to_city)
    key2 = (to_city, from_city)
    distance_km = mock_distances.get(key1, mock_distances.get(key2, 1000))

    if unit == "miles":
        distance = distance_km * 0.621371
    else:
        distance = distance_km

    return {
        "from": from_city,
        "to": to_city,
        "distance": round(distance, 2),
        "unit": unit,
        "timestamp": "2025-12-26T10:30:00Z"
    }

Checkpoint: Your tools now have proper validation and constraints.


Phase 4: Connect to Real APIs

Objective: Replace mock data with real API calls.

Task 4.1: Get API keys (optional but recommended)

Sign up for free API keys:

Task 4.2: Store API keys securely

Create .env file:

OPENWEATHER_API_KEY=your_key_here
EXCHANGE_RATE_API_KEY=your_key_here
ALPHA_VANTAGE_API_KEY=your_key_here

Update mcp_server.py:

import os
from dotenv import load_dotenv
import httpx

load_dotenv()

OPENWEATHER_API_KEY = os.getenv("OPENWEATHER_API_KEY")

Task 4.3: Update get_weather with real API

@mcp.tool()
async def get_weather(city: str, units: str = "celsius") -> dict:
    """Use this when user asks about weather conditions for a specific city."""

    if not OPENWEATHER_API_KEY:
        return {
            "error": "API key not configured",
            "city": city
        }

    # Map units to API format
    api_units = "metric" if units == "celsius" else "imperial"

    async with httpx.AsyncClient() as client:
        try:
            response = await client.get(
                "https://api.openweathermap.org/data/2.5/weather",
                params={
                    "q": city,
                    "appid": OPENWEATHER_API_KEY,
                    "units": api_units
                }
            )
            response.raise_for_status()
            data = response.json()

            return {
                "city": data["name"],
                "temperature": data["main"]["temp"],
                "condition": data["weather"][0]["description"],
                "humidity": data["main"]["humidity"],
                "wind": f"{data['wind']['speed']} m/s",
                "units": units,
                "timestamp": "2025-12-26T10:30:00Z"
            }
        except httpx.HTTPError as e:
            return {
                "error": f"Failed to fetch weather: {str(e)}",
                "city": city
            }

Important: Make the function async and update the server to handle async:

if __name__ == "__main__":
    uvicorn.run("mcp_server:mcp", host="0.0.0.0", port=8000, reload=True)

Task 4.4: Test with real data

Restart server and test with MCP Inspector. You should get real weather data!

Checkpoint: At least one tool fetches real data from an external API.


Phase 5: Error Handling & Edge Cases

Objective: Make your tools robust and user-friendly.

Task 5.1: Add comprehensive error handling

@mcp.tool()
async def get_weather(city: str, units: str = "celsius") -> dict:
    """Use this when user asks about weather conditions for a specific city."""

    # Input validation
    if not city or city.strip() == "":
        return {
            "error": "City name cannot be empty",
            "success": False
        }

    if units not in ["celsius", "fahrenheit"]:
        return {
            "error": f"Invalid units: {units}. Must be 'celsius' or 'fahrenheit'",
            "success": False
        }

    # API key check
    if not OPENWEATHER_API_KEY:
        return {
            "error": "Weather API is not configured. Using mock data.",
            "city": city,
            "temperature": 20,
            "condition": "Mock data",
            "success": False
        }

    # API call with error handling
    async with httpx.AsyncClient() as client:
        try:
            response = await client.get(
                "https://api.openweathermap.org/data/2.5/weather",
                params={
                    "q": city,
                    "appid": OPENWEATHER_API_KEY,
                    "units": "metric" if units == "celsius" else "imperial"
                },
                timeout=10.0
            )

            if response.status_code == 404:
                return {
                    "error": f"City '{city}' not found",
                    "success": False
                }

            response.raise_for_status()
            data = response.json()

            return {
                "success": True,
                "city": data["name"],
                "temperature": data["main"]["temp"],
                "condition": data["weather"][0]["description"],
                "humidity": data["main"]["humidity"],
                "wind": f"{data['wind']['speed']} m/s",
                "units": units,
                "timestamp": "2025-12-26T10:30:00Z"
            }

        except httpx.TimeoutException:
            return {
                "error": "Weather service timeout. Please try again.",
                "success": False
            }
        except httpx.HTTPError as e:
            return {
                "error": f"Weather service error: {str(e)}",
                "success": False
            }
        except Exception as e:
            return {
                "error": f"Unexpected error: {str(e)}",
                "success": False
            }

Task 5.2: Test error cases

Test these scenarios in MCP Inspector:

  • Empty city name: {"city": "", "units": "celsius"}
  • Invalid city: {"city": "InvalidCityXYZ123", "units": "celsius"}
  • Invalid units: {"city": "Paris", "units": "kelvin"}

Checkpoint: Your tools handle errors gracefully and return helpful messages.


Phase 6: Testing & Documentation

Objective: Document your tools and create a test suite.

Task 6.1: Create README.md

# MCP Protocol Explorer

A learning project demonstrating MCP server development with FastMCP.

## Tools

### get_weather
Get current weather conditions for a city.

**Parameters:**
- `city` (string, required): City name
- `units` (string, optional): "celsius" or "fahrenheit" (default: "celsius")

**Example:**
```json
{"city": "Tokyo", "units": "celsius"}

convert_currency

Convert amount between currencies.

Parameters:

  • amount (number, required): Amount to convert
  • from_currency (string, required): Source currency (USD, EUR, GBP, JPY, CAD)
  • to_currency (string, required): Target currency

Example:

{"amount": 100, "from_currency": "USD", "to_currency": "EUR"}

Setup

  1. Install dependencies: pip install -r requirements.txt
  2. (Optional) Add API keys to .env
  3. Run server: python mcp_server.py
  4. Test with MCP Inspector: npx @modelcontextprotocol/inspector

Testing

Connect MCP Inspector to http://localhost:8000


**Task 6.2: Create requirements.txt**

fastmcp>=0.1.0 uvicorn>=0.20.0 httpx>=0.24.0 python-dotenv>=1.0.0


**Task 6.3: Manual test checklist**

Create a test checklist and verify all items:

Manual Test Checklist: โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

Server Startup: โ–ก Server starts without errors โ–ก All tools are registered โ–ก Server responds to MCP Inspector

Tool Discovery: โ–ก All tools appear in Inspector โ–ก Tool names are descriptive โ–ก Descriptions start with โ€œUse this whenโ€ฆโ€ โ–ก Schemas show correct parameter types

Weather Tool: โ–ก Valid city returns real data โ–ก Invalid city returns error โ–ก Empty city returns error โ–ก Both units work correctly โ–ก Response includes all fields

Currency Tool: โ–ก Valid conversion works โ–ก Invalid currency returns error โ–ก Zero/negative amount handled โ–ก Rate calculation is correct

Stock Tool: โ–ก Valid symbol returns data โ–ก Invalid symbol handled gracefully โ–ก Response includes change percentage

Distance Tool: โ–ก Valid cities return distance โ–ก Both units (km/miles) work โ–ก Response is formatted correctly

Error Handling: โ–ก Network errors handled โ–ก Timeout errors handled โ–ก Invalid inputs rejected โ–ก Error messages are clear


**Checkpoint**: Your project is documented and tested.

---

## Testing Strategy

### Test Categories

**1. Tool Discovery Tests**
- Server lists all tools correctly
- Tool schemas are valid JSON Schema
- Descriptions are present and clear

**2. Happy Path Tests**
- Each tool works with valid inputs
- Responses have expected structure
- Data types are correct

**3. Error Handling Tests**
- Invalid parameters rejected
- Missing required parameters caught
- API failures handled gracefully

**4. Edge Case Tests**
- Empty strings
- Very large numbers
- Special characters in inputs
- Boundary values (min/max)

### Critical Test Cases

**Test Case 1: Basic Tool Invocation**
```json
Input: {"city": "Paris", "units": "celsius"}
Expected: Success response with temperature, condition, etc.

Test Case 2: Invalid Parameter Type

Input: {"city": 123, "units": "celsius"}
Expected: Schema validation error

Test Case 3: Missing Required Parameter

Input: {"units": "celsius"}
Expected: Error - city is required

Test Case 4: Optional Parameter Default

Input: {"city": "Paris"}
Expected: Success with units defaulting to "celsius"

Test Case 5: API Failure

Simulate: Disconnect internet
Expected: Graceful error message, not crash

Common Pitfalls & Debugging

Pitfall 1: Server Wonโ€™t Start

Symptom: ModuleNotFoundError: No module named 'fastmcp'

Solution:

# Ensure virtual environment is activated
source venv/bin/activate

# Reinstall dependencies
pip install -r requirements.txt

Pitfall 2: Tools Donโ€™t Appear in Inspector

Symptom: Inspector connects but shows no tools

Solutions:

  1. Check decorator: Must be @mcp.tool(), not @mcp.tool
  2. Check function is defined before if __name__ == "__main__"
  3. Restart server after changes

Pitfall 3: Type Hints Not Creating Schema

Symptom: Parameters show as โ€œanyโ€ type

Solution: Ensure youโ€™re using proper Python type hints:

# โœ… Correct
def my_tool(city: str, count: int = 5) -> dict:
    pass

# โŒ Wrong
def my_tool(city, count=5):
    pass

Pitfall 4: Async Function Not Awaited

Symptom: RuntimeWarning: coroutine 'get_weather' was never awaited

Solution: Make sure async functions are properly declared:

# โœ… Correct
@mcp.tool()
async def get_weather(city: str) -> dict:
    async with httpx.AsyncClient() as client:
        response = await client.get(...)

Pitfall 5: CORS Errors in Inspector

Symptom: Inspector canโ€™t connect, CORS errors in browser console

Solution: Add CORS middleware:

from fastapi.middleware.cors import CORSMiddleware

app = mcp.get_asgi_app()
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_methods=["*"],
    allow_headers=["*"],
)

Debugging Strategies

Strategy 1: Enable Debug Logging

import logging

logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)

@mcp.tool()
def my_tool(param: str) -> dict:
    logger.debug(f"my_tool called with param: {param}")
    # ...

Strategy 2: Test Outside MCP First

# Test your function directly
if __name__ == "__main__":
    import asyncio

    # Test function directly
    result = asyncio.run(get_weather("Paris", "celsius"))
    print(result)

Strategy 3: Use Inspector Network Tab

  • Open browser DevTools in Inspector
  • Watch Network tab for MCP requests/responses
  • Check for error status codes

Strategy 4: Validate JSON Schema

  • Use online JSON Schema validators
  • Paste FastMCP-generated schema
  • Check for syntax errors

Extensions & Challenges

Beginner Extensions

Extension 1: Add More Tools

  • Add 2-3 more tools (calculator, translator, timezone converter)
  • Practice different parameter patterns

Extension 2: Improve Error Messages

  • Make error messages more user-friendly
  • Include suggestions (e.g., โ€œDid you mean โ€˜New Yorkโ€™?โ€)

Extension 3: Add Tool Annotations

  • Mark read-only tools with readOnlyHint: True
  • Document which tools are safe vs. have side effects

Intermediate Extensions

Extension 4: Add Caching

  • Cache API responses for 5 minutes
  • Reduce API calls for repeated queries ```python from datetime import datetime, timedelta

cache = {}

@mcp.tool() async def get_weather(city: str) -> dict: cache_key = fโ€weather:{city}โ€ if cache_key in cache: cached_data, cached_time = cache[cache_key] if datetime.now() - cached_time < timedelta(minutes=5): return cached_data

# Fetch fresh data
data = await fetch_weather(city)
cache[cache_key] = (data, datetime.now())
return data ```

Extension 5: Add Rate Limiting

  • Limit requests to prevent API quota exhaustion
  • Return friendly error when limit exceeded

Extension 6: Multi-Language Support

  • Accept language parameter
  • Return responses in userโ€™s language

Advanced Challenges

Challenge 1: Tool Composition

  • Create a โ€œtrip plannerโ€ tool that calls multiple tools
  • Combines weather, distance, and conversion tools

Challenge 2: Streaming Responses

  • Implement a tool that streams data back
  • Useful for long-running operations

Challenge 3: Resource Integration

  • Add MCP resources (not just tools)
  • Expose user preferences, history, etc.

Challenge 4: Database Integration

  • Add SQLite database for persistence
  • Store query history, user preferences
  • Create tools that read/write to DB

Challenge 5: WebSocket Support

  • Add real-time updates via WebSockets
  • Push notifications for weather alerts

Real-World Connections

Industry Applications

1. Enterprise Integrations

  • Companies use MCP to connect ChatGPT to internal systems
  • Your tools โ†’ Customer databases, ticketing systems, analytics

2. SaaS Products

  • Many SaaS products are adding MCP servers
  • Allows AI to interact with their APIs

3. Developer Tools

  • IDEs and development platforms integrate MCP
  • AI assistants can use MCP tools for code analysis, deployment, etc.

4. Personal Productivity

  • Individual developers create MCP servers for personal workflows
  • Automate email, calendar, note-taking, etc.

After completing this project, youโ€™ll be ready for:

  • Project 2: Build UI widgets that display your tool outputs
  • Project 6: Add OAuth authentication to protect your tools
  • Project 9: Publish your MCP server as a ChatGPT App

Interview Relevance

This project demonstrates knowledge of:

  • API Design: RESTful patterns, parameter validation, error handling
  • Type Systems: JSON Schema, Python type hints, runtime validation
  • Integration Patterns: External API consumption, error recovery
  • Documentation: Clear descriptions, examples, usage guides
  • Testing: Manual testing, edge cases, error scenarios

Interview questions this prepares you for:

  • โ€œHow do you design APIs that AI models can use effectively?โ€
  • โ€œExplain JSON Schema and why it matters for validationโ€
  • โ€œHow do you handle errors in external API integrations?โ€
  • โ€œWhat makes a good tool description for an AI model?โ€

Resources

Books & Chapters

1. โ€œBuilding APIs You Wonโ€™t Hateโ€ by Phil Sturgeon

  • Chapter 2: Planning and Designing APIs
  • Chapter 7: Versioning
  • Chapter 10: Error Handling
  • Why: Teaches API design principles that apply to MCP tools

2. โ€œREST API Design Rulebookโ€ by Mark Massรฉ

  • Chapter 3: Interaction Design with HTTP
  • Chapter 5: Representation Design
  • Why: Helps design clear, consistent tool interfaces

3. โ€œPython Web Development with FastAPIโ€ by Bill Lubanovic

  • Chapter 2: Modern Python
  • Chapter 4: Pydantic and Type Hints
  • Why: FastMCP is built on FastAPI; understanding the foundation helps

Video Resources

1. MCP Protocol Overview

2. FastMCP Tutorial

3. JSON Schema Tutorial

Tools & Documentation

1. MCP Specification

2. FastMCP Documentation

3. MCP Inspector

4. JSON Schema Reference

5. OpenAPI/Swagger

1. Official MCP Examples

2. FastMCP Examples

  • Check examples/ folder in FastMCP repo
  • Simple, focused examples

3. ChatGPT Apps Examples


Self-Assessment Checklist

Use this checklist to verify youโ€™ve achieved the learning objectives:

Understanding (Theory)

  • I can explain what MCP is and why it exists
  • I understand the difference between tools, resources, and prompts
  • I can describe the request/response cycle between ChatGPT and an MCP server
  • I know why tool descriptions are critical for model behavior
  • I understand JSON Schema and its role in validation

Implementation (Practice)

  • Iโ€™ve created an MCP server with at least 3 tools
  • Iโ€™ve defined parameter schemas using type hints
  • Iโ€™ve tested all tools using MCP Inspector
  • Iโ€™ve connected at least one tool to a real external API
  • Iโ€™ve implemented error handling for invalid inputs and API failures

Best Practices (Quality)

  • My tool names are descriptive verb phrases
  • My descriptions start with โ€œUse this whenโ€ฆโ€
  • My tools follow the single responsibility principle
  • My outputs are structured and include metadata
  • Iโ€™ve added appropriate tool annotations (readOnlyHint)

Debugging (Skills)

  • I can diagnose why a tool isnโ€™t appearing in Inspector
  • I can fix JSON Schema validation errors
  • I can debug async/await issues
  • I know how to check server logs for errors

Completion Criteria

Youโ€™ve successfully completed this project when:

Minimum Viable Product (MVP)

โœ… Server is functional

  • Server starts without errors
  • All tools register correctly
  • MCP Inspector can connect

โœ… At least 3 tools implemented

  • Each tool has a clear purpose
  • Tool names follow conventions
  • Descriptions guide model behavior

โœ… Parameter validation works

  • Required parameters are enforced
  • Type validation catches errors
  • Optional parameters have defaults

โœ… Outputs are structured

  • Returns JSON objects (not plain strings)
  • Includes relevant metadata
  • Consistent structure across tools

โœ… Error handling is robust

  • Invalid inputs return clear errors
  • API failures donโ€™t crash server
  • Edge cases are handled

Stretch Goals (Optional)

๐ŸŽฏ Connected to real APIs

  • At least one tool fetches live data
  • API keys stored securely
  • Rate limiting implemented

๐ŸŽฏ Comprehensive testing

  • All edge cases tested manually
  • Test documentation created
  • Error scenarios verified

๐ŸŽฏ Well documented

  • README with setup instructions
  • Tool documentation with examples
  • Code comments explain non-obvious logic

๐ŸŽฏ One advanced feature

  • Caching implemented
  • Database integration
  • Tool composition
  • Streaming responses

Whatโ€™s Next?

After completing this project, youโ€™re ready for:

  1. Project 2: Hello World Widget
    • Build UI components that display your tool outputs
    • Learn the window.openai API
    • Create your first complete ChatGPT app
  2. Deepen MCP knowledge
    • Explore Resources (not just Tools)
    • Add Prompts to guide model behavior
    • Study advanced MCP servers in the wild
  3. Contribute to open source

Final Notes

Remember:

  • Tool descriptions are critical: Theyโ€™re how ChatGPT decides which tool to call
  • Keep tools focused: Single responsibility makes them reusable and reliable
  • Error handling matters: Users see these messages; make them helpful
  • Testing is essential: Use MCP Inspector religiously
  • Documentation helps future you: Write it while the code is fresh

Youโ€™ve learned:

  • The fundamentals of MCP
  • How to design effective AI tools
  • JSON Schema for validation
  • Error handling patterns
  • How to test and debug MCP servers

This knowledge is valuable because:

  • MCP is an emerging standard adopted by multiple AI platforms
  • Tool design skills transfer to any AI integration work
  • Understanding AI-API interaction patterns is a growing need
  • You can now build apps that reach 800+ million ChatGPT users

Happy building! Youโ€™re now ready to create AI-powered applications that extend ChatGPTโ€™s capabilities. Next up: Building the UI to make your tools shine.