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:
- Understand MCP fundamentals: Explain what the Model Context Protocol is, why it exists, and how it enables AI models to interact with external systems
- Design effective tools: Create tool definitions with clear names, descriptions, and parameter schemas that guide the modelโs behavior
- Implement JSON Schema validation: Define parameter types, constraints, and optional fields using JSON Schema patterns
- Test MCP servers: Use MCP Inspector to test tool invocations, examine request/response cycles, and debug issues
- Handle errors gracefully: Implement error handling that provides useful feedback to both the model and users
- Structure tool outputs: Design tool responses that are both human-readable and machine-parsable
- 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?
- Limited Knowledge: AI models like ChatGPT have a knowledge cutoff and canโt access real-time data
- No Actions: Models can only generate text; they canโt create calendar events, send emails, or update databases
- Platform Lock-in: Before MCP, each AI platform had its own proprietary integration format
- 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):
- 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
- Example:
- Resources: Structured documents the model can read
- Example:
user://profile,company://settings - Resources provide context and knowledge
- Often used for user-specific data
- Example:
- 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 effectsdestructiveHint: 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:
- Information Retrieval Tool: Fetches external data (e.g., weather, stock prices)
- Calculation/Conversion Tool: Performs computations (e.g., currency conversion, unit conversion)
- Parameterized Search Tool: Accepts multiple parameters with different types
- Tool with Optional Parameters: Demonstrates default values and optional fields
- 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
- Open the URL shown (usually http://localhost:5173)
- Enter your server URL:
http://localhost:8000 - Click โConnectโ
- You should see your
get_weathertool listed - Click it to view the schema
- 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:
- Weather: OpenWeatherMap (free tier)
- Currency: ExchangeRate-API (free tier)
- Stocks: Alpha Vantage (free tier)
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 convertfrom_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
- Install dependencies:
pip install -r requirements.txt - (Optional) Add API keys to
.env - Run server:
python mcp_server.py - 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:
- Check decorator: Must be
@mcp.tool(), not@mcp.tool - Check function is defined before
if __name__ == "__main__" - 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
languageparameter - 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.
Related Projects in This Course
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
- Anthropicโs MCP Introduction (if available)
- Official introduction to MCP concepts
2. FastMCP Tutorial
- FastMCP GitHub Repository
- Check README and examples
3. JSON Schema Tutorial
- Understanding JSON Schema
- Interactive guide to JSON Schema
Tools & Documentation
1. MCP Specification
- Model Context Protocol Docs
- Official protocol documentation
2. FastMCP Documentation
- FastMCP GitHub
- API reference and examples
3. MCP Inspector
- MCP Inspector GitHub
- Testing tool for MCP servers
4. JSON Schema Reference
- JSON Schema Documentation
- Complete schema reference
5. OpenAPI/Swagger
- OpenAPI Specification
- Similar concepts to MCP schemas
Related Projects & Examples
1. Official MCP Examples
- MCP Servers Repository
- Production-ready MCP server examples
2. FastMCP Examples
- Check
examples/folder in FastMCP repo - Simple, focused examples
3. ChatGPT Apps Examples
- OpenAI Apps SDK Examples
- Full apps with MCP servers + UI
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:
- Project 2: Hello World Widget
- Build UI components that display your tool outputs
- Learn the
window.openaiAPI - Create your first complete ChatGPT app
- Deepen MCP knowledge
- Explore Resources (not just Tools)
- Add Prompts to guide model behavior
- Study advanced MCP servers in the wild
- Contribute to open source
- Browse MCP Servers repository
- Find a useful tool to build
- Submit a PR to help others
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.