Quick Navigation
🏗️ MCP Server Structure
Module 3.2👋 Hello World MCP
Module 3.3🎨 Understanding Decorators and Schemas
Module 3.4🔢 Calculator Server
Module 3.5🌤️ Weather Lookup Server
Module 3.6📦 Adding Resources
Module 3.7📝 Adding Prompts
Module 3.8⚠️ Error Handling
Module 3.9🔍 Testing with MCP Inspector
🏗️ MCP Server Structure
Server anatomy: initialization, tool registration, and execution cycle.
An MCP Server consists of essential components: the FastMCP object that manages everything, the decorated functions that define tools, resources, and prompts, and the entry point that starts the server. Each part has a clear responsibility.
Knowing a server's anatomy lets you understand any MCP code you encounter and confidently create your own servers, knowing where each piece fits.
FastMCP, server object, decorators, entry point, components, responsibilities.
Starting an MCP Server involves creating a FastMCP instance with a name, configuring optional parameters such as version and description, and preparing the environment to receive connections. This is when everything gets registered.
A well-configured setup ensures your server works correctly from the start. Startup errors are the hardest to diagnose if you don’t understand the process.
FastMCP(), server name, configuration parameters, instantiation, initial setup.
Registering tools means decorating Python functions with @mcp.tool() to make them available to the AI model. The decorator automatically extracts the function’s name, description, and parameter schema.
Registering tools is the most fundamental operation of an MCP Server. Mastering this process is essential for creating servers that expose useful functionality to the model.
@mcp.tool(), automatic registration, function introspection, schema generation, Python decorators.
The lifecycle of an MCP Server includes: initialization, handshake with the Client, capability registration, waiting for requests, processing calls, and graceful shutdown. Each phase has specific events and behaviors.
Understanding the lifecycle lets you create robust servers that handle each phase correctly, including clean startup and shutdown.
Lifecycle, handshake, capabilities, request loop, graceful shutdown, lifecycle events.
A typical MCP Server file structure includes the server’s main file, a pyproject.toml or requirements.txt for dependencies, and optionally separate modules for complex tools. Simple servers can be a single file.
Good file organization makes maintenance, testing, and collaboration easier. Following conventions helps other developers understand your server quickly.
Project structure, main file, dependencies, pyproject.toml, modules, organization.
The essential settings for an MCP Server include the server name, version, transport used, and environment variables. These settings define how the server presents itself to the Client and how it behaves in different environments.
Configuring your server correctly ensures compatibility with different Hosts and environments. Incorrect settings are a common cause of connection problems.
Name and version, transport, environment variables, Host configuration, compatibility.
👋 Hello World MCP
Create your first server with a tool that returns 'Hello, world!'—the minimum viable setup.
The goal is to create the simplest MCP Server possible: a server with a single tool that returns "Hello, world!". This is the equivalent of Hello World for the MCP ecosystem—the minimum working example that proves everything is working.
Starting with the functional minimum eliminates complexity and lets you focus on what matters. Once Hello World works, you have a solid foundation for adding features.
Hello World, minimum viable implementation, proof of concept, learning foundation, simplicity.
Creating the server file involves defining the project directory, creating the main Python file (e.g., server.py), and ensuring the virtual environment is configured with the correct dependencies installed via pip.
Knowing how to create the initial structure correctly prevents import, dependency, and execution issues. It’s the foundation everything else is built on.
Project creation, server.py, virtual environment, pip install, directory structure.
The Hello World MCP code consists of importing FastMCP, creating the server instance, defining a function decorated with @mcp.tool() that returns "Hello, world!", and adding the entry point with mcp.run(). It's fewer than 10 lines of code.
Writing the code from scratch instead of copying and pasting ensures you understand every line. This is the foundational knowledge that will be expanded on in all the following modules.
from mcp.server.fastmcp import FastMCP, instantiation, @mcp.tool(), return string, mcp.run().
For Claude Desktop to recognize your server, you need to add it to claude_desktop_config.json. The entry includes the command to run Python, the path to the server file, and optional environment variables.
Host configuration is the step that connects your code to the AI model. Without it, your server exists, but no one can use it. It’s the bridge between development and real-world use.
claude_desktop_config.json, mcpServers, command, args, script path, restart Claude.
Testing Hello World involves restarting Claude Desktop, checking that the server appears in the connections list, and sending a message that triggers the tool. Claude should call your tool and display “Hello, world!” in the response.
Seeing your first server working is the most motivating moment in the course. Confirming that everything works gives you the confidence to build more complex servers.
Manual testing, connection verification, tool calling, expected response, basic troubleshooting.
A line-by-line analysis of the Hello World code: the import brings in the framework, FastMCP() creates the server, @mcp.tool() registers the function, the docstring becomes the tool description, return defines the response, and mcp.run() starts the event loop.
Understanding every line in depth turns memorized code into real knowledge. You'll know how to safely modify and expand any part of the server.
Import statement, instantiation, decorator pattern, docstring as metadata, event loop, execution.
🎨 Understanding Decorators and Schemas
How FastMCP uses Python decorators to register tools and define parameters.
Python decorators are functions that wrap other functions, adding behavior without modifying the original code. The @ symbol before a function indicates that it is being decorated. In MCP, decorators register functions as tools, resources, or prompts.
Decorators are FastMCP’s core mechanism. Without understanding them, the code seems like magic. With this knowledge, you control exactly what’s happening.
Python decorators, @syntax, wrapper functions, metaprogramming, automatic registration.
The @mcp.tool() decorator turns a regular Python function into an MCP tool. It extracts the function name, its docstring as the description, and the parameter type hints as the input schema. It also accepts optional parameters to customize the registration.
@mcp.tool() is the decorator you'll use in 90% of cases. Mastering its options and behavior lets you create precise, well-documented tools.
@mcp.tool(), automatic naming, docstring as description, type hints, optional parameters.
Parameter schemas define the input types a tool accepts. FastMCP uses Python type hints (str, int, float, bool, list, dict) to automatically generate the JSON Schema the model uses to build correct calls.
Well-defined schemas ensure the model sends data in the correct format. Without proper schemas, your tools receive unexpected data and fail silently.
JSON Schema, type hints, str, int, float, bool, Optional, required vs. optional parameters.
MCP tools can return simple strings, dictionaries serialized as JSON, or lists of content with specific types (text, image, etc.). The return type determines how the model interprets and presents the result to the user.
Choosing the right return type affects how the model uses the result. Well-formatted returns allow the model to generate more accurate and useful responses.
Return a string, return a dict, TextContent, ImageContent, serialization, response formatting.
Tool descriptions come from the Python functions’ docstrings. The model reads these descriptions to decide when and how to use each tool. Clear, specific descriptions help the model use your tools more intelligently and accurately.
Description quality directly affects the quality of interactions. A poorly described tool will be ignored or misused by the model.
Docstrings, tool descriptions, parameter descriptions, prompt engineering for tools, clarity.
MCP tool best practices include descriptive snake_case names, detailed docstrings, type hints for all parameters, default values when appropriate, informative return values, and consistent error handling.
Following best practices from the start builds habits that lead to more robust servers that are easier to maintain and more effective at interacting with the model.
Naming conventions, documentation, type safety, default values, error handling, consistency.
🔢 Calculator Server
4 tools (add, subtract, multiply, divide) with input validation.
Planning the calculator's tools involves defining 4 mathematical operations (add, subtract, multiply, divide), their parameters (two numbers), input types (float), and special behaviors such as validating division by zero.
Planning before coding is an essential habit. Defining the tools' interface before implementing them ensures consistency and avoids rework.
API planning, interface definition, consistent parameters, edge cases, validation.
Implementing addition and subtraction as MCP tools involves creating decorated functions that take two float parameters, perform the operation, and return the formatted result. They are the simplest tools and serve as a model for the next ones.
Starting with the simplest operations lets you focus on the implementation pattern. Once addition and subtraction work, multiplication and division follow the same model.
Tool implementation, float parameters, formatted return value, descriptive docstrings, replicable pattern.
The multiplication tool follows the same pattern as the previous tools, but introduces an opportunity to discuss formatting results with many decimal places and presenting large numbers in a readable way for the model.
The multiplication seems trivial, but it raises practical questions about formatting and numerical precision that matter for any tool that works with numbers.
Multiplication, number formatting, decimal precision, large numbers, result presentation.
The division tool introduces the first real input validation case: checking whether the divisor is zero before performing the operation. This demonstrates how to handle errors elegantly and return informative messages to the model.
Input validation is a critical skill for any server. Division by zero is the perfect example for introducing how tools should handle invalid inputs.
Input validation, division by zero, error message, conditional return, robustness.
Testing all calculator tools involves checking each operation with different types of input: integers, decimals, negative numbers, zero, and very large numbers. It includes testing division by zero to confirm that validation works.
Comprehensive testing ensures that your server works in every scenario. Making a habit of testing with varied inputs helps prevent bugs in production.
Manual testing, test cases, edge cases, varied inputs, validation checks.
Improving the calculator with robust error handling includes adding try/except to catch unexpected exceptions, validating input types, limiting the size of accepted numbers, and returning standardized error messages the model can interpret.
Turning a working prototype into robust code is a professional skill. This step shows you how to evolve your server from “works” to “always works.”
Try/except, robust validation, input limits, standardized error messages, defensive programming.
🌤️ Weather Lookup Server
Tool that queries an external API and returns formatted data.
Choosing a free and reliable weather API is the first step in building the server. APIs like Open-Meteo offer weather data without requiring an API key, making setup simpler and more accessible for learning.
Knowing how to choose and evaluate external APIs is an essential skill. Many MCP Servers in practice are wrappers that connect AI models to existing APIs.
Weather API, Open-Meteo, free API, endpoints, API documentation, rate limits.
Making HTTP requests inside an MCP Server requires asynchronous libraries such as httpx, since FastMCP runs on an asyncio event loop. The requests fetch data from the API and return JSON responses that need to be parsed.
HTTP requests are the foundation of any integration with external services. Mastering asynchronous calls ensures your server doesn’t hang while waiting for API responses.
httpx, async/await, GET request, JSON parsing, headers, query parameters.
Implementing the weather tool involves creating a decorated async function that takes the city name as a parameter, makes the API request, extracts the relevant data (temperature, humidity, condition), and returns a formatted string with the information.
This is the first tool that connects your server to the outside world. The pattern of receiving input, fetching data, and formatting a response is used in 80% of real MCP Servers.
async def, city parameter, API call, data extraction, response formatting.
Formatting the response means transforming raw API data into an organized, readable string. This includes presenting temperature with units, weather conditions as descriptive text, and additional information in a structured way for the model to interpret.
Formatting quality determines how well the model interprets and presents data to the user. Well-formatted data leads to more natural and informative responses.
Data formatting, f-strings, units of measurement, information structuring, readability.
Network errors are inevitable: the API may be down, the connection may drop, or the response may come in an unexpected format. Handling these errors involves try/except for httpx.HTTPError, configurable timeouts, and informative error messages.
Tools that depend on external APIs need to be resilient. Without error handling, a network failure takes down the entire server instead of returning a helpful message.
HTTPError, timeout, ConnectionError, retry logic, fallback, user-friendly error message.
Testing with Claude involves asking about the weather in different cities and checking that the model calls the tool correctly, receives the data, and presents the information naturally to the user. It includes testing with nonexistent cities to validate error handling.
Testing with Claude is the real integration test. It shows how the model decides when to use your tool and how it interprets the results it returns.
Integration testing, interaction with Claude, real tool calling, end-to-end validation, error scenarios.
📦 Adding Resources
Expose static and dynamic data as Resources for the model to consume.
In code, Resources are functions decorated with @mcp.resource() that return data when accessed through a specific URI. Unlike tools, resources don’t perform actions—they expose information that the model can read to enrich its context.
Resources are the second pillar of an MCP Server. Implementing them in code builds on the theoretical knowledge from Track 1 and expands your server’s capabilities.
@mcp.resource(), URI, data return, context exposure, difference from tools.
Static resources always return the same content — such as documentation, guides, default settings, or any data that doesn’t change between calls. They’re the simplest to implement and ideal for reference context.
Static resources are the simplest way to provide rich context to the model. They can improve response quality by giving the model always-available reference information.
Static content, documentation, settings, reference guides, immutable data.
Dynamic resources calculate or retrieve data on each access—such as the current system status, data from a database, or information that changes over time. They use the same URIs but return updated content.
Dynamic resources let the model access real-time data without tool calls. They’re ideal for dashboards, status, and frequently changing information.
Dynamic content, real-time data, on-demand calculations, automatic updates, freshness.
URI schemes define how resources are identified and accessed. Each resource has a unique URI (e.g., config://app/settings, data://users/list) that the Client uses to request its content. The scheme indicates the type, and the path indicates the specific resource.
Well-designed URIs make your resources intuitive and organized. A good URI scheme makes it easy for the model to discover and use resources.
URI, scheme, path, unique identification, naming conventions, resource hierarchy.
The MCP protocol lets the Client list all resources available on the Server via resources/list. This enables automatic discovery: the model knows what data is available without needing external documentation.
The resource listing is what makes the protocol self-documenting. The model can explore the available resources and choose the most relevant ones for each situation.
resources/list, automatic discovery, self-documentation, resource metadata, dynamic listing.
Testing resources involves checking that they appear in the list, their URIs are accessible, the returned content is correct, and dynamic resources update their data. You can test via Claude or the MCP Inspector.
Testing resources ensures the model has access to the correct data. Broken resources can cause the model to generate responses based on outdated or incorrect information.
Resource testing, URI verification, correct content, dynamic updates, debugging.
📝 Adding Prompts
Create reusable prompts that the model can invoke.
In code, Prompts are functions decorated with @mcp.prompt() that return predefined message templates. They can accept parameters for customization and return a list of messages that guide the model’s behavior.
Prompts are the third pillar of MCP and its most underused. Implementing them in code completes your mastery of the three capabilities of an MCP Server.
@mcp.prompt(), message templates, parameters, message list, role (user/assistant).
A simple prompt is a function that returns a fixed message without accepting parameters. For example, a prompt that instructs the model to act as a code reviewer or debugging assistant, with detailed instructions about the expected behavior.
Simple prompts are the most accessible starting point. They demonstrate the mechanism without the complexity of parameters, letting you focus on structure and format.
Prompt with no parameters, fixed message, behavior instruction, return format, simplicity.
Prompts with parameters accept arguments that customize the generated message. For example, a code review prompt that takes the programming language and review focus as parameters, generating specific instructions for each case.
Parameterized prompts are much more versatile than fixed prompts. They let you create reusable templates that adapt to different contexts with a single piece of code.
Prompt parameters, dynamic templates, f-strings in prompts, customization, reuse.
Like tools and resources, prompts can be listed by the Client via prompts/list. The listing shows each prompt’s name, description, and accepted parameters, allowing the Host to offer the user a selection interface.
The prompt listing is how the Host discovers which workflows are available. Well-documented prompts appear clearly in the user interface.
prompts/list, prompt discovery, metadata, parameter descriptions, Host interface.
The best use cases for MCP prompts include standardized workflows (code review, data analysis), onboarding new users, domain-specific instructions, and templates that combine resource context with instructions for using tools.
Knowing when to use prompts vs. tools vs. resources is an important design decision. Prompts shine in scenarios where the model’s behavior needs to be guided consistently.
Standardized workflows, onboarding, domain instructions, combining with tools and resources, consistency.
Testing prompts involves checking that they appear in the list, accept parameters correctly, and that the generated messages guide the model as expected. The test includes checking the model's behavior when the prompt is activated.
Poorly tested prompts can make the model behave unexpectedly. Testing ensures the instructions are clear and produce the desired behavior.
Prompt testing, parameter validation, behavior checks, instruction quality, iteration.
⚠️ Error Handling
How to handle failures, timeouts, and invalid inputs gracefully.
Errors in MCP Servers fall into categories: input errors (invalid parameters), runtime errors (logic failures), network errors (inaccessible APIs), system errors (out of memory, disk full), and protocol errors (malformed messages).
Classifying errors is the first step to handling them correctly. Each type of error requires a different handling strategy and an appropriate response message.
Input errors, runtime errors, network errors, system errors, classification, strategy.
Input validation is the process of checking that the received parameters are valid before running the tool's logic. This includes checking types, ranges, string formats, list sizes, and any conditions the data must meet.
Input validation is the first line of defense against errors. Rejecting bad inputs is easier and safer than dealing with the cascading errors they cause during execution.
Input validation, type checking, range checking, early return, clear rejection messages.
Using try/except in tools lets you catch exceptions during execution and return helpful error messages instead of crashes. The recommended pattern is to catch specific exceptions, log the error, and return a message the model can use to inform the user.
A well-implemented try/except is the difference between a server that crashes and one that recovers gracefully. Unhandled exceptions can bring down the entire MCP server.
try/except, specific exceptions, logging, recovery, error message as return value.
Network errors include connection refused, DNS resolution failure, connection timeout, and HTTP responses with error status codes. Each type requires specific handling: retry for transient errors, fallback for prolonged outages.
If your server depends on external APIs, network errors are inevitable. Knowing how to handle them ensures your server remains useful even when external services fail.
ConnectionError, TimeoutError, HTTP status codes, retry, fallback, circuit breaker.
Timeouts limit how long an operation can take before it is canceled. In MCP tools, it's essential to configure timeouts for network calls, disk operations, and any processing that could stall. asyncio.wait_for() is the standard tool.
Without timeouts, a tool can hang indefinitely, blocking the server and frustrating the user. Timeouts ensure the server always responds within a reasonable time.
asyncio.wait_for(), configurable timeout, TimeoutError, cancellation, response time.
Logging in MCP Servers uses Python’s logging module to record events, errors, and debug information. Logs should go to stderr (not stdout, which is used by the protocol). Levels such as DEBUG, INFO, WARNING, and ERROR help filter information.
Without logs, debugging an MCP Server is like navigating in the dark. Good logs are essential for diagnosing problems in development and production.
logging module, stderr vs stdout, log levels, formatting, debugging in production, traceability.
🔍 Testing with MCP Inspector
Use MCP Inspector to debug and test tools without needing Claude.
The MCP Inspector is an official tool that lets you connect to an MCP Server and interactively test its tools, resources, and prompts without needing an AI model. It works as an MCP Client with a visual interface.
The Inspector is the most efficient tool for developing and debugging MCP Servers. It lets you test quickly without relying on Claude Desktop, speeding up the development cycle.
MCP Inspector, testing tool, visual interface, MCP Client, rapid development.
You can run the MCP Inspector via npx without a permanent installation. The command npx @modelcontextprotocol/inspector starts the tool and opens a web interface where you can connect to your server and explore its capabilities.
Installing and running the Inspector quickly is a practical skill that speeds up your entire MCP development workflow. It’s a tool you’ll use constantly.
npx, @modelcontextprotocol/inspector, web interface, no permanent installation, Node.js.
Connecting the Inspector to your server involves specifying the command to start the server (e.g., python server.py), the transport (usually STDIO), and clicking Connect. The Inspector performs the MCP handshake and lists all available capabilities.
Knowing how to connect the Inspector to your server is the essential first step to using it. The connection immediately reveals whether there are startup or configuration issues with your server.
STDIO connection, execution command, handshake, capabilities, troubleshooting.
The Inspector lets you select any listed tool, fill in its parameters manually, and run it. The result appears in the interface with full details: returned content, execution time, and any errors that occurred.
Testing tools in the Inspector is much faster than testing via Claude. You can iterate quickly, test edge cases, and verify that your tools work correctly before integrating them.
Manual execution, parameter entry, result viewing, execution time, rapid iteration.
Analyzing responses in the Inspector means examining the content returned by tools, resources, and prompts in raw format. You can see exactly the JSON the server sends, including content types, metadata, and response structure.
Viewing the raw responses lets you understand exactly what the model receives. This is crucial for optimizing formatting and ensuring the responses are interpretable.
Raw responses, JSON, content types, metadata, formatting, output analysis.
The Inspector shows detailed errors when something goes wrong: Python stack traces, protocol errors, timeouts, and connection issues. Combined with server logs, it lets you quickly identify the root cause of any problem.
Efficient debugging is what separates a productive developer from one who gets stuck for hours. Inspector is your main tool for diagnosing and resolving problems in MCP Servers.
Stack traces, protocol errors, diagnostics, root cause, fix, debug-fix-test cycle.