Recommended resources
The freely available resources this lesson will direct you to.
Hello! Welcome to the next step in your journey into AI agents.
In our last lesson, we explored the architecture of the Model Context Protocol (MCP), contrasting its decoupled client-server model with the tightly-coupled nature of direct function calling. We established what MCP is and why its design philosophy promotes scalability, security, and portability.
Today, we transition from architectural theory to hands-on practice. This lesson directly addresses the learning outcome: Set up an MCP server that exposes tools to an LLM client. You will write the code for a functioning MCP server, define tools for it, run it locally, and connect it to a real-world client application.
1. From Architecture to Implementation
Let's begin by grounding our practical work in the concepts from the previous lesson. An MCP server's job is to expose capabilities—tools, resources, and prompts—to a client.
The following video provides an excellent walkthrough of how these conceptual primitives translate into code.
Model Context Protocol (MCP) Explained in 20 Minutes
This segment from the video 'Model Context Protocol (MCP) Explained in 20 Minutes' by Shaw Talebi details the key primitives an MCP server can offer. It will help you understand the different types of capabilities we can build.
Watch the segment from 02:27 to 08:03. Focus on the definitions of the three key services an MCP server can provide: prompts, resources, and tools. Pay attention to the coffee shop analogy and how the server responds to client requests.
As the video explained, while prompts and resources are valuable, tools are the heart of an agent's ability to act. A tool is an arbitrary function that allows the LLM, via the client, to perform an action in the real world, like calling an API or, as we'll see, writing an email draft.
2. Building Your First MCP Server in Python
Now, let's get to the core of today's lesson: building the server. We will use Python and Anthropic's official anthropic-mcp-sdk for this. The process involves four main steps:
- Setting up the environment.
- Defining the server and its tools.
- Choosing a transport mechanism.
- Running the server.
Step 1: Environment Setup
First, you'll need to install the necessary library. You can do this using pip:
pip install anthropic-mcp-sdk
The videos also mention uv, a fast Python package manager. While it's excellent for managing complex environments (as seen in the video examples), a simple pip install is sufficient for our purposes today.
Step 2: Coding the Server and its Tools
Create a new Python file, for example, my_mcp_server.py. We will build a server that exposes a single, practical tool: write_email_draft.
The video provides a complete walkthrough of building a server. We'll follow its structure, but I'll provide the code here with explanations so you can build it step-by-step.
Model Context Protocol (MCP) Explained in 20 Minutes
The following segments from the same video show the entire process of coding the server, defining a prompt, resources, and a tool, and then running it.
Watch from 08:03 to 15:40. This is the main coding part of the lesson. Follow along as the presenter: Initializes the FastMCP server. Adds a prompt template using the @mcp.prompt decorator. Adds resources (file contents) using the @mcp.resource decorator. Implements the write_email_draft tool with @mcp.tool. Notice the importance of the docstring for the LLM. Configures the stdio transport and runs the server.
Let's consolidate that into a single script. Here is the code for my_mcp_server.py. This example defines a tool to create a draft email in Gmail.
Note: For this specific tool to work, you would need to set up the get_gmail_service() helper function and the necessary Google API credentials as mentioned in the video. For this lesson, the key is to understand the MCP structure, even if you don't fully implement the Gmail authentication.
# my_mcp_server.py
from mcp.server.fastmcp import FastMCP
import os # For a simple example, we'll simulate the Gmail service
# --- A Simple Simulation of the Gmail Service ---
# In a real application, this would handle OAuth2 and API calls.
def get_gmail_service():
print("MCP_SERVER: Authenticating with Gmail service...")
# In a real scenario, this would return a service object.
# For our example, we'll return a mock object.
class MockGmailService:
def users(self):
return self
def drafts(self):
return self
def create(self, userId, body):
print(f"MCP_SERVER: Creating draft with body: {body}")
class MockExecute:
def execute(self):
print("MCP_SERVER: Draft created successfully in Gmail.")
return {"message": {"id": "draft-123"}}
return MockExecute()
return MockGmailService()
# ----------------------------------------------------
# 1. Initialize the FastMCP server
mcp = FastMCP("my_first_server", version="0.1.0")
# 2. Define a tool using the @mcp.tool() decorator
@mcp.tool()
async def write_email_draft(to: str, subject: str, body: str) -> str:
"""
Creates a draft email in the user's Gmail account.
Args:
to: The email address of the recipient.
subject: The subject line of the email.
body: The content of the email.
"""
try:
# In the video, this uses a helper function to get the service
# service = get_gmail_service()
# We will simulate the action for simplicity
print(f"MCP_SERVER: Tool 'write_email_draft' called.")
print(f"MCP_SERVER: Recipient: {to}, Subject: {subject}")
# Simulate creating the draft
# In a real implementation, you'd build the API request here
# service.users().drafts().create(userId='me', body=message).execute()
return f"Successfully created a draft email to {to} with the subject '{subject}'."
except Exception as e:
return f"An error occurred: {e}"
# 3. Add a resource for context
@mcp.resource(uri="file:///email_templates/follow_up.md")
def follow_up_template() -> str:
"""An example of a follow-up email template."""
return """
Subject: Following up on our call
Hi [Name],
Just wanted to follow up on our conversation earlier. I've summarized the key points we discussed:
- [Point 1]
- [Point 2]
Let me know your thoughts.
Best,
[Your Name]
"""
# 4. Run the server with the stdio transport layer
if __name__ == "__main__":
print("MCP Server starting with stdio transport...")
mcp.run(transport="stdio")
Key takeaways from the code:
@mcp.tool(): This decorator is what registers your Python function as a tool that the MCP server can expose.- Type Hints and Docstrings:
async def write_email_draft(to: str, subject: str, body: str) -> str:and its docstring are not just good practice. MCP uses them to generate a schema that tells the LLM what the tool is called, what it does, and what arguments it expects. This is how the LLM knows how to use your tool. @mcp.resource(...): This decorator exposes static content, like a template file, to the LLM. Theuriis a unique identifier the LLM can use to request this content.mcp.run(transport="stdio"): This line starts the server.stdio(Standard Input/Output) is a transport mechanism where the client application communicates with your server script directly through its input and output streams. It's perfect for local development and testing.
3. Connecting the Server to an LLM Client
Your server is now ready, but it's just a script waiting for a connection. To use it, you need an MCP-compliant client. We'll use Claude for Desktop, as shown in the video.
To connect your server, you need to tell the Claude app how to run it. This is done by editing a configuration file.
The official MCP documentation provides the exact format for configuring a client. We'll look at the Python and general configuration sections.
Read the section titled 'Testing your server with Claude for Desktop'. Focus on the JSON structure within the claude_desktop_config.json file. Note the command and args fields, and the instruction to use an absolute path.
Based on the documentation, here's how you'd configure Claude for Desktop:
- Find or create the config file: On macOS, it's at
~/Library/Application Support/Claude/claude_desktop_config.json. On Windows, it would be in a similar AppData location. - Edit the file to add your server configuration. You need to provide the command to execute your Python environment and the absolute path to your script.
Here is an example of what the JSON configuration should look like:
{
"mcpServers": {
"my_first_server": {
"command": "/path/to/your/virtual/environment/bin/python",
"args": [
"/path/to/your/project/my_mcp_server.py"
],
"description": "A server with a tool to draft emails."
}
}
}
Crucially, you must replace the placeholder paths with the absolute paths on your system.
command: The full path to the Python executable in your virtual environment (or your system's Python).args: A list containing the full path to yourmy_mcp_server.pyscript.
After saving the file, completely quit and restart Claude for Desktop. The client will now launch your Python script in the background and communicate with it via stdio.
You can now test it!
Model Context Protocol (MCP) Explained in 20 Minutes
Let's see the final result. This part of the video shows how to add the configuration and test the server within Claude's UI.
Watch from 15:40 to 19:44. Observe how the server is added to the config file, how the tools and resources appear in the Claude UI, and how a prompt triggers the LLM to use the exposed tool.
When you open a new chat in Claude, you should now see your integrations available. You can ask it something like:
"Draft an email to 'test@example.com' with the subject 'Hello from MCP' and the body 'This is a test.'"
The LLM will recognize that this task matches the write_email_draft tool, ask for your permission to run it, and then execute your Python script to perform the action.
Conclusion
Congratulations! You have successfully built and connected your first MCP server. You've taken the abstract client-server architecture and turned it into a tangible, working system.
Key Takeaways:
- An MCP server is a program that exposes tools, resources, and prompts to clients.
- In Python, the
anthropic-mcp-sdkand its@mcp.tool()decorator make it easy to define tools. - Docstrings and type hints are critical, as they form the schema that the LLM uses to understand and call your tools.
- The
stdiotransport is a simple and effective way to run and test a server locally. - Client applications like Claude for Desktop are configured with a JSON file that tells them how to launch your server process.
You've built a local server, which is a fantastic first step. However, for production systems, you'll need more robust deployment strategies. In our next lesson, we will explore this further by looking at common LLM deployment options, including the differences between API-based and self-hosted models, which sets the stage for deploying MCP servers in the cloud.