← Claude Code & Certification

Introduction to Model Context Protocol - Certification Study Notes

Table of Contents

Introduction to Model Context Protocol - Certification Study Notes

Course: Anthropic’s “Introduction to Model Context Protocol” Date: April 2026 Difficulty: Intermediate Focus: Building MCP servers and clients, understanding protocol fundamentals


Module 1: Introduction to MCP

What is MCP?

Model Context Protocol (MCP) is an open-source standard for connecting AI applications to external systems. Think of it like USB-C for AI — providing a standardized interface for AI apps (like Claude) to access data, tools, and workflows.

Core Purpose:

  • Enable AI applications to connect to data sources (databases, files, APIs)
  • Allow AI to invoke actions via tools (execute functions, make API calls)
  • Provide interaction templates via prompts (pre-crafted instructions)

Ecosystem Support: MCP is supported by Claude, ChatGPT, VS Code, Cursor, and many other tools. Build once, integrate everywhere.

Why MCP Matters

For Developers: Reduced complexity when integrating with AI applications. Standard interfaces eliminate custom integrations.

For AI Applications: Access to diverse data, tools, and workflows enhances capabilities and user experience.

For End Users: More capable AI assistants that can access their data and take actions securely.

MCP Architecture Diagram (ASCII)

┌─────────────────────────────────────────────────────────────┐
│                    MCP HOST (AI Application)                │
│                    (Claude, ChatGPT, VS Code)               │
│                                                             │
│  ┌──────────────────────────────────────────────────────┐  │
│  │          MCP Client Manager                         │  │
│  │  ┌──────────────┐  ┌──────────────┐  ┌─────────────┐│  │
│  │  │ MCP Client 1 │  │ MCP Client 2 │  │MCP Client N││  │
│  │  └──────┬───────┘  └──────┬───────┘  └──────┬──────┘│  │
│  └─────────┼──────────────────┼──────────────────┼──────┘  │
│            │                  │                  │         │
└────────────┼──────────────────┼──────────────────┼─────────┘
             │                  │                  │
    ┌────────▼────────┐ ┌───────▼───────┐ ┌──────▼─────────┐
    │  MCP Server A   │ │ MCP Server B   │ │ MCP Server C   │
    │  (Local STDIO)  │ │(Local STDIO)   │ │(Remote HTTP)   │
    │                 │ │                │ │                │
    │ - Tools         │ │ - Tools        │ │ - Tools        │
    │ - Resources     │ │ - Resources    │ │ - Resources    │
    │ - Prompts       │ │ - Prompts      │ │ - Prompts      │
    └─────────────────┘ └────────────────┘ └────────────────┘

Each MCP Client maintains a dedicated connection to its server.

Key Participants

  1. MCP Host — The AI application coordinating connections (Claude, VS Code, etc.)
  2. MCP Client — Component maintaining connection to a server (one per server)
  3. MCP Server — Program providing context (runs locally or remotely)

The 3 Core Primitives

MCP defines three fundamental primitives that servers expose to clients:

1. Tools — Model-Controlled Actions

What they are: Executable functions that the AI can invoke to perform actions.

Who controls: The language model (with user approval).

Analogy: Like POST endpoints in REST APIs — model requests execution.

When to use:

  • Actions that require LLM reasoning
  • Operations the AI should execute based on context
  • Anything that modifies external state

How they work:

  1. Server defines tools with JSON Schema
  2. Client lists available tools
  3. LLM decides which tools to call
  4. Client executes the tool
  5. Results returned to LLM

Example Use Cases:

  • Database queries
  • API calls
  • File operations
  • Calculations
  • External service integrations

2. Resources — Application-Controlled Data

What they are: File-like data sources that clients can read (static or templated).

Who controls: The application/server (client reads only).

Analogy: Like GET endpoints — immutable information lookup.

When to use:

  • Configuration data
  • Reference information
  • Database schemas
  • API documentation
  • Static files

How they work:

  1. Server defines resources (with optional URI templates)
  2. Client lists available resources
  3. Client reads specific resources by URI
  4. Server returns data with MIME type

Example Use Cases:

  • Database schema documentation
  • Configuration files
  • API reference materials
  • Log files
  • Cached data

3. Prompts — User-Controlled Templates

What they are: Pre-written instruction templates for accomplishing specific tasks.

Who controls: The user (selects which prompt to use).

Analogy: Like workflow templates — user-driven structured interactions.

When to use:

  • Standardized workflows
  • Expert prompts for specific domains
  • Few-shot examples
  • System prompts for specialized tasks

How they work:

  1. Server defines prompts with optional arguments
  2. Client lists available prompts
  3. User selects a prompt
  4. Server returns the prompt template
  5. User/LLM uses it as context

Example Use Cases:

  • Code review prompts
  • Writing assistance workflows
  • Domain-specific expert prompts
  • Standardized analysis templates

Comparison Table: Tools vs Resources vs Prompts

AspectToolsResourcesPrompts
Who ControlsLLM (automated)Application (read-only)User (interactive)
AnalogyPOST endpointGET endpointTemplate library
ExampleCalculate sumFetch schemaCode review guide
ExecutionAutomatic (LLM decides)On-demand readUser-selected
State ChangeCan modify external stateRead-onlyNo direct action
Discoverytools/listresources/listprompts/list
Invocationtools/callresources/readprompts/get

Best Practices

  1. Protocol Version Negotiation — Both client and server must agree on compatible versions during initialization
  2. Capability Discovery — Clients should discover what features servers support (tools, resources, prompts, notifications)
  3. Error Handling — Always validate responses and handle connection failures gracefully
  4. Logging Strategy — Use stderr for STDIO servers, never write to stdout
  5. Security — Validate all external inputs, use proper authentication mechanisms

Module 2: Hands-On MCP Server Development

Project Setup

System Requirements

  • Python 3.10+ (or equivalent Node.js/TypeScript)
  • uv package manager (or npm for Node.js)
  • Basic understanding of async/await patterns

Quick Start (Python)

# Install uv
curl -LsSf https://astral.sh/uv/install.sh | sh

# Create project
uv init my-mcp-server
cd my-mcp-server

# Set up environment
uv venv
source .venv/bin/activate

# Install MCP SDK
uv add "mcp[cli]" httpx

Building a Complete MCP Server

Core Concepts

FastMCP Framework:

  • Simplifies MCP server development using Python decorators
  • Automatically generates JSON Schema from type hints
  • Handles JSON-RPC protocol complexity
  • Supports async/await natively

Basic Server Structure

from typing import Any
import httpx
from mcp.server.fastmcp import FastMCP

# Initialize FastMCP server
mcp = FastMCP("my-server")

# Constants
API_BASE = "https://api.example.com"
USER_AGENT = "my-server/1.0"

def main():
    # Start the server
    mcp.run(transport="stdio")

if __name__ == "__main__":
    main()

Key Points:

  • FastMCP takes a server name as argument
  • transport="stdio" uses standard input/output
  • The server handles all JSON-RPC protocol details automatically

Defining Tools

Tools are the most common primitive. They enable the LLM to execute actions.

Tool Definition Using Decorators

@mcp.tool()
async def add(a: int, b: int) -> int:
    """Add two numbers together.
    
    Args:
        a: First number
        b: Second number
    
    Returns:
        The sum of a and b
    """
    return a + b


@mcp.tool()
async def get_weather(latitude: float, longitude: float) -> str:
    """Get weather forecast for a location.
    
    Args:
        latitude: Latitude of location (-90 to 90)
        longitude: Longitude of location (-180 to 180)
    
    Returns:
        Weather forecast as formatted string
    """
    url = f"https://api.weather.gov/points/{latitude},{longitude}"
    
    async with httpx.AsyncClient() as client:
        response = await client.get(url)
        data = response.json()
        
    forecast_url = data["properties"]["forecast"]
    forecast_response = await client.get(forecast_url)
    forecast_data = forecast_response.json()
    
    periods = forecast_data["properties"]["periods"]
    
    result = []
    for period in periods[:5]:
        result.append(f"{period['name']}: {period['detailedForecast']}")
    
    return "\n".join(result)

Decorator Mechanics:

  1. @mcp.tool() registers the function as a callable tool
  2. Type hints become JSON Schema constraints
  3. Docstring becomes tool description
  4. Parameter descriptions in docstring become JSON Schema
  5. Return type defines output schema

Important Rules:

  • Tool names must be valid identifiers (lowercase_with_underscores)
  • All parameters must have type hints
  • Docstrings are mandatory (describe purpose and parameters)
  • Return values should be simple types (str, int, float) or JSON-serializable

Real-World Tool Example: Weather Server

async def make_nws_request(url: str) -> dict[str, Any] | None:
    """Make request to National Weather Service API with error handling."""
    headers = {
        "User-Agent": "weather-app/1.0",
        "Accept": "application/geo+json"
    }
    async with httpx.AsyncClient() as client:
        try:
            response = await client.get(url, headers=headers, timeout=30.0)
            response.raise_for_status()
            return response.json()
        except Exception:
            return None


def format_alert(feature: dict) -> str:
    """Format alert feature into readable string."""
    props = feature["properties"]
    return f"""
Event: {props.get("event", "Unknown")}
Area: {props.get("areaDesc", "Unknown")}
Severity: {props.get("severity", "Unknown")}
Description: {props.get("description", "No description available")}
Instructions: {props.get("instruction", "No instructions provided")}
"""


@mcp.tool()
async def get_alerts(state: str) -> str:
    """Get weather alerts for a US state.
    
    Args:
        state: Two-letter US state code (e.g., CA, NY, TX)
    
    Returns:
        Formatted list of active weather alerts
    """
    NWS_API_BASE = "https://api.weather.gov"
    url = f"{NWS_API_BASE}/alerts/active/area/{state}"
    data = await make_nws_request(url)
    
    if not data or "features" not in data:
        return "Unable to fetch alerts or no alerts found."
    
    if not data["features"]:
        return "No active alerts for this state."
    
    alerts = [format_alert(feature) for feature in data["features"]]
    return "\n---\n".join(alerts)


@mcp.tool()
async def get_forecast(latitude: float, longitude: float) -> str:
    """Get weather forecast for a location.
    
    Args:
        latitude: Latitude of the location (range: -90 to 90)
        longitude: Longitude of the location (range: -180 to 180)
    
    Returns:
        5-period weather forecast
    """
    NWS_API_BASE = "https://api.weather.gov"
    
    # Get forecast grid endpoint
    points_url = f"{NWS_API_BASE}/points/{latitude},{longitude}"
    points_data = await make_nws_request(points_url)
    
    if not points_data:
        return "Unable to fetch forecast data for this location."
    
    # Get actual forecast
    forecast_url = points_data["properties"]["forecast"]
    forecast_data = await make_nws_request(forecast_url)
    
    if not forecast_data:
        return "Unable to fetch detailed forecast."
    
    # Format periods
    periods = forecast_data["properties"]["periods"]
    forecasts = []
    for period in periods[:5]:
        forecast = f"""{period["name"]}:
Temperature: {period["temperature"]}°{period["temperatureUnit"]}
Wind: {period["windSpeed"]} {period["windDirection"]}
Forecast: {period["detailedForecast"]}"""
        forecasts.append(forecast)
    
    return "\n---\n".join(forecasts)

Defining Resources

Resources expose read-only data to clients.

Resource Definition Using Decorators

@mcp.resource("config://app")
def get_config() -> str:
    """Get application configuration.
    
    Returns:
        Current configuration as formatted string
    """
    config = {
        "version": "1.0.0",
        "debug": True,
        "timeout": 30,
        "max_retries": 3
    }
    return "\n".join(f"{k}: {v}" for k, v in config.items())


@mcp.resource("schema://database")
def get_schema() -> str:
    """Get database schema documentation.
    
    Returns:
        Schema definition and table descriptions
    """
    schema = """
DATABASE SCHEMA
================
Tables: users, products, orders
    
USERS TABLE:
- id (INTEGER PRIMARY KEY)
- name (VARCHAR(255))
- email (VARCHAR(255) UNIQUE)
- created_at (TIMESTAMP)

PRODUCTS TABLE:
- id (INTEGER PRIMARY KEY)
- name (VARCHAR(255))
- price (DECIMAL(10, 2))
- inventory (INTEGER)

ORDERS TABLE:
- id (INTEGER PRIMARY KEY)
- user_id (INTEGER FOREIGN KEY)
- product_id (INTEGER FOREIGN KEY)
- quantity (INTEGER)
- order_date (TIMESTAMP)
"""
    return schema


@mcp.resource("logs://app/{date}")
def get_logs(date: str) -> str:
    """Get application logs for specific date.
    
    Args:
        date: Date in YYYY-MM-DD format
    
    Returns:
        Log entries for that date
    """
    # In real implementation, read from file or database
    return f"Logs for {date}:\n[2026-04-24 10:00] Server started\n[2026-04-24 10:05] Request processed"

Resource URI Format:

  • config://app — Read configuration
  • schema://database — Read database schema
  • logs://app/{date} — Templated resource with parameters

Key Differences from Tools:

  • Resources are read-only (no side effects)
  • Parameters are in URI, not function arguments
  • Return values should be strings or JSON-serializable data
  • Clients don’t require “approval” to read resources

Defining Prompts

Prompts are user-controlled interaction templates.

Prompt Definition Using Decorators

@mcp.prompt()
def code_review_prompt(code: str) -> str:
    """Code review expert prompt.
    
    Args:
        code: Code snippet to review
    
    Returns:
        Expert code review prompt template
    """
    return f"""You are an expert code reviewer. Review the following code for:
1. Performance issues
2. Security vulnerabilities
3. Code style and readability
4. Potential bugs
5. Best practices

CODE TO REVIEW:

{code}


Provide detailed feedback with suggestions for improvement."""


@mcp.prompt()
def writing_assistant_prompt(topic: str) -> str:
    """Writing assistant prompt for blog posts.
    
    Args:
        topic: Blog topic
    
    Returns:
        Structured writing prompt
    """
    return f"""You are a professional blog writer. Write a comprehensive blog post about "{topic}" with:

1. Engaging introduction (2-3 paragraphs)
2. Clear main sections (4-5 major points)
3. Real-world examples and case studies
4. Actionable takeaways
5. Compelling conclusion

Target audience: Technical professionals
Tone: Informative yet accessible
Length: 1500-2000 words"""


@mcp.prompt()
def sql_expert_prompt(question: str, schema: str) -> str:
    """SQL query expert prompt.
    
    Args:
        question: Natural language question
        schema: Database schema information
    
    Returns:
        Expert SQL prompt
    """
    return f"""You are a SQL expert. Answer the following question by writing an efficient SQL query.

DATABASE SCHEMA:
{schema}

QUESTION: {question}

Provide:
1. The SQL query
2. Explanation of how it works
3. Performance considerations
4. Alternative approaches if applicable"""

Key Characteristics:

  • Accept parameters from users
  • Return formatted instruction strings
  • Help structure LLM interactions
  • No side effects (purely informational)

Server Inspector for Debugging

The MCP Inspector is a development tool for testing servers without building a full client.

Starting the Inspector

# Install MCP CLI tools
uv add mcp

# Start inspector for your server
mcp run server.py

Inspector Features

  1. Tool Discovery — Lists all available tools with schemas
  2. Tool Testing — Execute tools with test parameters
  3. Resource Access — Browse and read resources
  4. Protocol Debugging — View JSON-RPC messages
  5. Error Analysis — See detailed error responses

Example Inspector Session

MCP Server Inspector
====================

Available Tools:
1. get_alerts (state: str) -> str
2. get_forecast (latitude: float, longitude: float) -> str

Available Resources:
1. config://app
2. schema://database
3. logs://app/{date}

Available Prompts:
1. code_review_prompt
2. writing_assistant_prompt
3. sql_expert_prompt

>> Tool: get_alerts
   Parameter: state = "CA"
   
   Result: Event: Winter Storm...
           Severity: Moderate...

>> Resource: config://app
   Result: version: 1.0.0
           debug: True...

Best Practices for Servers

  1. Error Handling — Always catch exceptions and return meaningful error messages
  2. Type Hints — Use precise type hints for automatic schema generation
  3. Documentation — Write clear docstrings (becomes user-facing)
  4. Logging — Use stderr for STDIO servers, never stdout
  5. Async/Await — Use async properly for I/O-bound operations
  6. Resource Cleanup — Close connections, clean up files
  7. Input Validation — Validate parameters before processing
  8. Timeout Handling — Set reasonable timeouts on external API calls

Running the Server

def main():
    # With stdio transport (for Claude Desktop, local clients)
    mcp.run(transport="stdio")
    
    # Or with SSE transport (for HTTP-based clients)
    # mcp.run(transport="sse", host="localhost", port=8000)

if __name__ == "__main__":
    main()

Module 3: Connecting with MCP Clients

Building an MCP Client

System Requirements

  • Python 3.8+ or Node.js 16+
  • Anthropic API key for Claude integration
  • Understanding of async programming

Client Setup (Python)

# Create project
uv init mcp-client
cd mcp-client

# Set up environment
uv venv
source .venv/bin/activate

# Install dependencies
uv add mcp anthropic python-dotenv

Complete Python Client Example

Basic Client Structure

import asyncio
from typing import Optional
from contextlib import AsyncExitStack

from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
from anthropic import Anthropic
from dotenv import load_dotenv

load_dotenv()

class MCPClient:
    def __init__(self):
        self.session: Optional[ClientSession] = None
        self.exit_stack = AsyncExitStack()
        self.anthropic = Anthropic()

    async def connect_to_server(self, server_script_path: str):
        """Connect to an MCP server via stdio transport.
        
        Args:
            server_script_path: Path to server script (.py or .js)
        """
        is_python = server_script_path.endswith('.py')
        is_js = server_script_path.endswith('.js')
        
        if not (is_python or is_js):
            raise ValueError("Server script must be .py or .js")
        
        command = "python" if is_python else "node"
        
        # Create server parameters
        server_params = StdioServerParameters(
            command=command,
            args=[server_script_path],
            env=None
        )
        
        # Set up transport
        stdio_transport = await self.exit_stack.enter_async_context(
            stdio_client(server_params)
        )
        self.stdio, self.write = stdio_transport
        
        # Create session
        self.session = await self.exit_stack.enter_async_context(
            ClientSession(self.stdio, self.write)
        )
        
        # Initialize connection
        await self.session.initialize()
        
        # List available tools
        response = await self.session.list_tools()
        tools = response.tools
        print(f"\nConnected with tools: {[tool.name for tool in tools]}")

Query Processing Logic

    async def process_query(self, query: str) -> str:
        """Process a query using Claude and available MCP tools.
        
        Args:
            query: User's natural language query
        
        Returns:
            Final response from Claude
        """
        messages = [
            {
                "role": "user",
                "content": query
            }
        ]
        
        # Get available tools
        response = await self.session.list_tools()
        available_tools = [{
            "name": tool.name,
            "description": tool.description,
            "input_schema": tool.inputSchema
        } for tool in response.tools]
        
        # Initial Claude API call
        response = self.anthropic.messages.create(
            model="claude-sonnet-4-20250514",
            max_tokens=1000,
            messages=messages,
            tools=available_tools
        )
        
        # Process response and handle tool calls
        final_text = []
        
        for content in response.content:
            if content.type == 'text':
                final_text.append(content.text)
            
            elif content.type == 'tool_use':
                tool_name = content.name
                tool_args = content.input
                
                # Execute tool through MCP server
                result = await self.session.call_tool(tool_name, tool_args)
                final_text.append(f"[Executed {tool_name}]")
                
                # Build tool result message
                messages.append({
                    "role": "assistant",
                    "content": [content]
                })
                
                messages.append({
                    "role": "user",
                    "content": [
                        {
                            "type": "tool_result",
                            "tool_use_id": content.id,
                            "content": result.content
                        }
                    ]
                })
                
                # Get Claude's response to tool result
                response = self.anthropic.messages.create(
                    model="claude-sonnet-4-20250514",
                    max_tokens=1000,
                    messages=messages,
                    tools=available_tools
                )
                
                final_text.append(response.content[0].text)
        
        return "\n".join(final_text)

Interactive Chat Loop

    async def chat_loop(self):
        """Run interactive chat interface."""
        print("\nMCP Client Started!")
        print("Type your queries or 'quit' to exit.\n")
        
        while True:
            try:
                query = input("Query: ").strip()
                
                if query.lower() == 'quit':
                    break
                
                response = await self.process_query(query)
                print(f"\n{response}\n")
            
            except Exception as e:
                print(f"\nError: {str(e)}\n")
    
    async def cleanup(self):
        """Clean up resources."""
        await self.exit_stack.aclose()

Main Entry Point

async def main():
    if len(sys.argv) < 2:
        print("Usage: python client.py <path_to_server_script>")
        sys.exit(1)
    
    client = MCPClient()
    try:
        await client.connect_to_server(sys.argv[1])
        await client.chat_loop()
    finally:
        await client.cleanup()

if __name__ == "__main__":
    import sys
    asyncio.run(main())

Running the Client

# Run with Python server
uv run client.py ./server/weather.py

# Run with Node.js server
uv run client.py ./server/build/index.js

# With absolute path
uv run client.py /Users/username/mcp-servers/weather.py

Transport Mechanisms

MCP supports two transport protocols:

1. Stdio Transport (Local)

Use When:

  • Server and client on same machine
  • Developing/testing locally
  • Using Claude Desktop
  • Low-latency required

Advantages:

  • Zero network overhead
  • Optimal performance
  • Simple process management
  • No authentication needed

Implementation:

from mcp.client.stdio import stdio_client
from mcp import StdioServerParameters

server_params = StdioServerParameters(
    command="python",
    args=["server.py"]
)

transport = await stdio_client(server_params)

2. Streamable HTTP Transport (Remote)

Use When:

  • Server runs remotely
  • Multiple clients accessing one server
  • Cloud deployment required
  • Need HTTP authentication

Advantages:

  • Remote server support
  • Multiple concurrent clients
  • Standard HTTP authentication
  • Scalable architecture

Supports:

  • Bearer token authentication
  • API key authentication
  • OAuth (recommended)
  • Custom headers

Implementation:

from mcp.client.sse import sse_client

session = SSEClientSession(
    uri="https://mcp-server.example.com",
    auth=BearerToken("your-api-key")
)

await session.initialize()
tools = await session.list_tools()

Client Best Practices

  1. Error Handling — Wrap tool calls in try-catch, handle timeouts
  2. Resource Management — Use async context managers for cleanup
  3. Tool Validation — Validate tool names match server’s tools
  4. Message Flow — Build proper message history for context
  5. Security — Store API keys in environment variables
  6. Testing — Test with MCP Inspector before building client

Module 4: Assessment Concepts

Understanding Protocol Flow

Complete Request-Response Cycle

CLIENT                          SERVER
  |                               |
  |------ initialize ------>      |
  |                               |
  |<----- initialize result -----|
  |                               |
  |------ tools/list ---->        |
  |                               |
  |<----- tools result ---------|
  |                               |
  |------ tools/call ------>      |
  |   (execute get_weather)       |
  |                               |
  |                      [processing...]
  |                               |
  |<----- tool result ----------|
  |                               |

JSON-RPC 2.0 Message Format

Request:

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/list"
}

Response:

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "tools": [
      {
        "name": "get_weather",
        "description": "Get weather for location",
        "inputSchema": {
          "type": "object",
          "properties": {
            "location": {"type": "string"}
          },
          "required": ["location"]
        }
      }
    ]
  }
}

Integration with Claude Code

Claude Code MCP Configuration (settings.json)

{
  "mcpServers": {
    "weather": {
      "command": "python",
      "args": ["/absolute/path/to/weather.py"],
      "env": {
        "API_KEY": "your-key"
      }
    },
    "database": {
      "command": "uv",
      "args": ["--directory", "/path/to/db-server", "run", "db.py"]
    },
    "remote-service": {
      "url": "https://api.example.com/mcp",
      "auth": "bearer",
      "token": "your-token"
    }
  }
}

Key Configuration Points:

  • command — How to start the server (python, node, uv, etc.)
  • args — Command-line arguments
  • env — Environment variables for the server
  • url — For remote (HTTP) servers
  • auth — Authentication type (bearer, api-key, oauth)

Security Considerations

  1. Input Validation — Always validate parameters before processing
  2. API Key Management — Use environment variables, never hardcode
  3. HTTPS for Remote Servers — Always use TLS for HTTP transport
  4. Authentication — Implement proper auth (OAuth, bearer tokens)
  5. Error Messages — Don’t expose sensitive info in error responses
  6. Logging — Be careful not to log API keys or credentials
  7. Tool Permissions — Consider which tools users should access

Common Pitfalls and Solutions

PitfallProblemSolution
stdout loggingCorrupts JSON-RPC messages in STDIOUse stderr or logging library
Type hint missingAuto-schema fails, tools not registeredAdd type hints to all parameters
Docstring missingNo description in tool listAlways write docstrings
Unhandled exceptionsServer crashes silentlyWrap code in try-catch
Missing awaitAsync function not executedUse await for all async calls
Hardcoded pathsBreaks on different machinesUse absolute paths or relative to cwd
No timeoutsHanging requestsSet timeouts on external API calls
Resource leaksFiles/connections not closedUse context managers
Invalid JSONProtocol errorsEnsure all responses are JSON-serializable
Tool name conflictsTools shadow each otherUse unique, descriptive names

Debugging Tips

  1. Use MCP Inspector — Test tools without building full client
  2. Enable Logging — Log to stderr to see execution flow
  3. Test Schemas — Validate tool input schemas with sample data
  4. Check Types — Ensure return values match declared types
  5. Test Edge Cases — Empty inputs, null values, large data
  6. Monitor Performance — Check timeout and response times
  7. Verify Connections — Test both stdio and HTTP transports
  8. Review Messages — Examine JSON-RPC messages for errors

Key Learning Objectives Summary

By completing this course, you should understand:

✓ MCP architecture and how clients connect to servers ✓ The 3 core primitives: Tools, Resources, Prompts ✓ When and how to use each primitive ✓ Building MCP servers with FastMCP (decorators, type hints) ✓ Implementing tools, resources, and prompts ✓ Testing servers with MCP Inspector ✓ Building MCP clients that integrate with Claude ✓ Handling tool execution and message flows ✓ Transport mechanisms (STDIO vs HTTP) ✓ Security considerations and best practices ✓ Debugging and troubleshooting servers and clients ✓ Integrating MCP with Claude Code (settings.json configuration)


Practice Exercises

Exercise 1: Build a Simple Calculator Server

Create an MCP server with tools for:

  • add(a, b) — Add two numbers
  • multiply(a, b) — Multiply two numbers
  • divide(a, b) — Divide two numbers (with error handling for zero)

Include a resource config://calculator that returns calculator settings.

Exercise 2: Build a Client for Calculator Server

Create a client that:

  1. Connects to the calculator server
  2. Lists available tools
  3. Allows user to input calculations
  4. Executes tools and displays results
  5. Handles errors gracefully

Exercise 3: Extend Weather Server with Prompts

Add prompts to the weather server:

  • severe_weather_alert — Analyzes severe weather and suggests precautions
  • travel_planning — Suggests travel timing based on forecasts

Exercise 4: Test with Claude Desktop

  1. Build a simple MCP server (any domain)
  2. Configure Claude Desktop with claude_desktop_config.json
  3. Test server integration with Claude
  4. Verify tools appear in Claude interface
  5. Execute tools from Claude conversation

Additional Resources

Official Documentation:

Reference Implementations:

Related Concepts to Study:

  • JSON-RPC 2.0 specification
  • Async/await patterns
  • REST API design principles
  • Protocol buffers and serialization
  • Process management and IPC

Study Tips for Certification

  1. Build First, Then Integrate — Create a simple server before testing with Claude
  2. Use the Inspector — Test every tool with MCP Inspector before deploying
  3. Read the Spec — Understand JSON-RPC and protocol details
  4. Practice Error Handling — Test with invalid inputs, network errors
  5. Review Type Hints — Understand how Python type hints map to JSON Schema
  6. Study Async Patterns — Master async/await for I/O operations
  7. Test Transport Mechanisms — Try both STDIO and HTTP transports
  8. Review Security — Understand authentication and input validation
  9. Build Real Servers — Connect to actual APIs and services
  10. Document Everything — Write clear docstrings and comments

Last Updated: April 2026 Course Status: Available Certification Available: Yes