Podcast Title

Author Name

0:00
0:00
Album Art

MCP Explained in 10 Minutes: A Python Developer's Crash Course

By 10xdev team August 09, 2025

If you're a Python developer building with LLMs and want to learn how to integrate MCP (the Model Context Protocol), then this article is for you. I'm going to walk you through a complete crash course to understand what we can do with MCP as GenAI developers.

In this article, I won't just show you how to build a simple server and integrate it into an application like Claude Desktop. This is a developer-first tutorial. You will learn how to set up your own MCP servers using the Python SDK and also how to integrate those servers into your own Python application and backend, so you can start building AI systems and agents around it.

The goal with this article is to help you cut through the noise and focus on what you can actually use to build production-ready AI systems and agents. So let's dive in.

This tutorial is structured to provide a comprehensive overview, starting with some theory first. While you can jump straight to the code, I don't recommend it if you're new to MCP, as understanding the fundamentals is crucial.

We will walk through several chapters: 1. Understanding MCP: What it is and why it's gaining traction. 2. Technical Deep Dive: The core components and concepts. 3. Server Setup: Creating your first MCP server. 4. OpenAI Integration: Connecting MCP with LLMs. 5. MCP vs. Function Calling: A practical comparison. 6. Docker Deployment: Running your server in a container. 7. Lifecycle Management: Handling connections properly.

Let's dive straight into our introduction and context.

Part 1: Introduction and Context

For those completely new to it, MCP stands for Model Context Protocol. Over the past few months, it has been gaining a lot of popularity, but it has been out since November 2024. It was developed by Anthropic, who introduced MCP as a "new standard for connecting AI assistants to systems where data lives, including content repositories, business tools, and development environments."

When first introduced, it was a bit hard to wrap your head around what it exactly was. Is it a new model? New capabilities? Is it part of Claude? Can we integrate it with OpenAI? These are common questions for developers looking to apply it to real-world projects.

With major new updates in the world of AI happening constantly, it's wise to be hesitant about chasing every shiny new object. MCP might have seemed like one of those, but then its popularity exploded.

Looking at Google Trends for the keyword "MCP," you can see that from the beginning of March 2025, interest popped off exponentially. MCP started appearing everywhere—on social media, blogs, and in developer communities. So, what happened to cause this surge in interest?

To explain that, we first need to understand what MCP is at a high level. A quick search reveals many diagrams trying to explain it. The core idea, as defined by Anthropic, is that MCP is a new standard for connecting AI assistants to systems where data lives.

This can be illustrated by comparing the situation before and after MCP.

Before MCP: For the last few years, developers have been building applications around LLMs. To connect an app to an external service like Slack, Google Drive, or GitHub, we would create a custom API layer. For Slack, you might have a send_message function. To integrate this with an LLM, you'd either use custom logic or, for dynamic use, function calling with a tool definition provided to the LLM. This is the established method.

This is a key point: fundamentally, MCP doesn't introduce any new functionality to an LLM or to building AI systems that we couldn't already do. It's a new way of doing things.

After MCP: Now, we have a protocol that unifies the API made available to an LLM. The benefit is that instead of every developer creating their own definitions for integrating with Slack or Google Drive, we have a standard protocol that defines how to specify schemas, functions, documentation, and arguments. When you provide this to the MCP layer, your AI applications can seamlessly integrate with it via a unified API.

Understanding that MCP is a standardized way of making tools and resources available to LLMs helps explain its recent rise. MCP is a technically solid and lightweight protocol, and more companies and developers have started to realize its potential. If more people use MCP, we can standardize development and make our lives easier by not reinventing the wheel every time we connect AI services to common tools.

This trend has led to hundreds of officially supported servers from major tech companies, making it very easy for developers to integrate their AI systems with tools like Slack, GitHub, and Google Drive. The hype continued, with even OpenAI, Anthropic's biggest competitor, supporting MCP in their agent SDK. This has led to MCP essentially "winning," as outlined in an excellent blog post by Leighton Space.

Despite some critique on social media about overusing MCP for problems where it isn't needed, the number one thing MCP has going for it is its adoption rate. The power of any new protocol derives from its ecosystem. If we look at the GitHub star history of other popular AI frameworks, the exponential trend of MCP is clear and will likely overtake others in the coming months.

Now that we understand what MCP does from a high level, you might be wondering, "Okay, but what can we as developers do with it?" Let's cover that now.

Part 2: Understanding MCP at a Technical Level

This is the last theoretical part, but there are some fundamental things you need to understand to work with MCP effectively. Let's cover some common terminology using concepts from the official MCP documentation.

  • Hosts: These are programs like Claude Desktop, IDEs, or, more interestingly for developers, your own Python application that wants to access data through the MCP protocol.
  • MCP Clients: These are protocol clients that maintain a one-on-one connection with your servers.
  • MCP Servers: These are lightweight programs that each expose specific capabilities through the standardized protocol. They can expose tools, resources, and prompts.

These three components make up the core architecture of MCP. Through MCP, you can also connect with local data sources and remote services via APIs.

Let's look at a diagram to better understand this: - On the left, we have the Host with the MCP client (e.g., Claude Desktop, your custom Python backend). - In the middle, we have Servers (e.g., Server A, B, C), each with a different purpose and set of tools. - The Host connects to a server via the MCP client over the MCP protocol to interact with it.

There's a clear distinction between using MCP for local tools (as a personal AI assistant in an application like Claude Desktop) and you as a developer creating a server and a client application that connects to it. This is a key distinction we will cover further.

On your MCP server, you can define application-specific logic. Think of this as any Python function—a simple calculation, uppercasing a string, or connecting to a database. You create a function, and that logic becomes a utility on the server, available to your host application.

When working with MCP as a developer, you need to understand three things: 1. Setting up a server: This can be a custom-built server (which we'll do) or a pre-existing one. 2. Setting up a host application: And connecting it to your server through a client. Our client will be a custom Python backend. 3. Connecting data sources: How to connect local or remote services to a server via Python. This is just writing standard Python functions.

One more thing you need to understand about MCP—perhaps the most important—is the concept of its two transport mechanisms: Standard I/O and Server-Sent Events (SSE).

Initially, it might seem that everything happens on the same machine. The MCP server and host live on the same computer. This can feel like a cumbersome way to define tools for your LLM—spinning up a server just to connect to it from an application on the same machine, rather than just importing a tools.py file.

This is where the different transport mechanisms come in. Most tutorials use the Standard I/O method, where everything lives on your local machine. However, you can also take your server and put it somewhere else entirely—on a remote server on a different machine—and connect to it via Server-Sent Events (SSE) using HTTP. Now your MCP server is accessible via an API.

This is visualized as follows: - Local Development (Standard I/O): The host and server are on the same machine. This is effective for personal AI assistants in desktop apps. - Remote Development (SSE/HTTP): The server is on a different machine. You could have one server maintaining all your tools, and various client applications connecting to it. You could also have multiple servers and multiple clients.

This is a fundamental component to understand. For custom Python backends, the remote development model offers the most utility. We will focus on this, as many tutorials don't cover how to set up a server via Docker, connect to it, and build real applications.

Part 3: Setting Up Your First Server

The good news for Python developers is that there's an official Python SDK for MCP. This makes our lives much easier. All you need is to add the mcp-cli package to your environment.

Let's set up our first server. Here is a very simple server example, around 31 lines of Python code.

# simple_server.py
from mcp import FastMCP, tool

# 1. Initialize the MCP Server
# For SSE transport, host and port are needed. For Standard I/O, they are not.
transport_mode = "stdio" # or "sse"
mcp = FastMCP(
    "simple-calculator",
    host="127.0.0.1" if transport_mode == "sse" else None,
    port=8050 if transport_mode == "sse" else None,
)

# 2. Add a tool to the server using a decorator
@mcp.tool
def add(a: int, b: int) -> int:
    """Adds two numbers together."""
    return a + b

# 3. Run the server
if __name__ == "__main__":
    if transport_mode == "sse":
        mcp.run(transport="sse")
    else:
        mcp.run(transport="stdio")

The purpose of this code is not only to teach you how to set up servers but also to act as a boilerplate.

To set up an MCP server, you just do mcp = FastMCP(...). This is very similar to FastAPI, and the design principles were likely inspired by it. You can give it a name, and for HTTP, a host and port.

To add tools, you use the @mcp.tool decorator on a function. The function add becomes a tool on our server. This can be any Python function, from connecting to a database to sending a message on Slack.

The script includes logic to run the server with either SSE or Standard I/O. For demonstration, we'll start with Standard I/O.

You can run this server in development mode to inspect and test it using the MCP inspector: bash mcp dev server.py This will spin up the server and an inspector interface where you can connect to your server, list the available tools, and test them. For our example, you would see the add tool with its description and parameters.

The inspector is great for debugging and exploring other server features like prompts and resources. You can define local data files as resources or pre-define prompts on the server, but for now, the most significant value is in the tools.

Now, let's see how to connect to this server from our Python application using a client.

# client_standard_io.py
import asyncio
from mcp.client import Client

async def main():
    # Define server parameters for Standard I/O
    server_params = ["python", "server.py"]

    async with Client(server_command=server_params) as session:
        print("Connected to the server.")

        # List available tools
        tools = await session.list_tools()
        print(f"Available tools: {[t.name for t in tools]}")

        # Call the 'add' tool
        result = await session.call_tool("add", a=2, b=4)
        print(f"Result of add(2, 4): {result.result}")

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

Here, we use the Python SDK to connect to the server. We define the command to run the server, and the Client handles spinning it up. Inside the async with block, we create a session to interact with the server, list its tools, and call them.

You might be thinking, "This still feels like a cumbersome way to make a function available." And you're right. There's no AI integration yet. MCP is a standardized way of transferring information.

Next, let's cover the SSE transport mechanism via HTTP. I'll change the transport_mode in server.py to "sse". Now, the server needs to be running independently before the client can connect.

First, run the server: bash python server.py It will be running on localhost:8050.

The client code is similar, but the connection is established via a URL.

# client_sse.py
import asyncio
from mcp.client import Client

async def main():
    # Connect to the running server via HTTP
    server_url = "http://127.0.0.1:8050"

    async with Client(server_url=server_url) as session:
        # ... same logic as the standard I/O client
        tools = await session.list_tools()
        print(f"Available tools: {[t.name for t in tools]}")
        result = await session.call_tool("add", a=2, b=4)
        print(f"Result of add(2, 4): {result.result}")

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

If you try to run this client without the server running, you'll get a connection error. This setup is what you'd use if your server was deployed on a different machine.

Part 4: OpenAI Integration

By now, you understand the fundamentals of MCP. This knowledge alone probably puts you in the top 5% of developers working with it. Now, let's see how to build applications around LLMs using MCP.

We'll spin up a new server that emulates Retrieval-Augmented Generation (RAG).

# rag_server.py
import json
from mcp import FastMCP, tool

mcp = FastMCP("rag-server")

@mcp.tool
def get_knowledge_base() -> str:
    """Retrieves the entire knowledge base as a formatted string."""
    with open("data/kb.json") as f:
        data = json.load(f)
    return json.dumps(data)

if __name__ == "__main__":
    mcp.run(transport="stdio")

The server has one tool, get_knowledge_base, which reads a JSON file representing a company knowledge base.

Here's an example of data/kb.json: json [ {"q": "What's our company vacation policy?", "a": "Employees get 20 days of paid vacation per year."}, {"q": "What are the office hours?", "a": "Office hours are from 9 AM to 5 PM, Monday to Friday."} ]

Now for the client, which will integrate with OpenAI.

# openai_client.py
import os
import asyncio
from openai import OpenAI
from mcp.client import Client

class MCPOpenAIClient:
    def __init__(self):
        self.openai_client = OpenAI(api_key=os.environ.get("OPENAI_API_KEY"))
        self.model = "gpt-4o"
        self.session = None

    async def connect_server(self):
        server_params = ["python", "rag_server.py"]
        self.session = Client(server_command=server_params)
        await self.session.__aenter__()
        print("Connected to server.")

    async def get_mcp_tools(self):
        mcp_tools = await self.session.list_tools()
        # Convert MCP tools to OpenAI's required format
        openai_tools = []
        for tool in mcp_tools:
            openai_tools.append(tool.to_openai_tool())
        return openai_tools

    async def process_query(self, query: str):
        messages = [{"role": "user", "content": query}]
        tools = await self.get_mcp_tools()

        # First API call to see if the model wants to use a tool
        response = self.openai_client.chat.completions.create(
            model=self.model,
            messages=messages,
            tools=tools,
            tool_choice="auto",
        )
        response_message = response.choices[0].message

        # If the model chose a tool, call it
        if response_message.tool_calls:
            messages.append(response_message)
            tool_call = response_message.tool_calls[0]
            function_name = tool_call.function.name

            # Call the tool via the MCP session
            tool_result = await self.session.call_tool(function_name)

            messages.append({
                "tool_call_id": tool_call.id,
                "role": "tool",
                "name": function_name,
                "content": tool_result.result,
            })

            # Second API call with the tool's result for synthesis
            second_response = self.openai_client.chat.completions.create(
                model=self.model,
                messages=messages,
            )
            return second_response.choices[0].message.content
        else:
            return response_message.content

    async def close(self):
        if self.session:
            await self.session.__aexit__(None, None, None)

async def main():
    client = MCPOpenAIClient()
    await client.connect_server()
    answer = await client.process_query("What is our company vacation policy?")
    print(f"Answer: {answer}")
    await client.close()

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

This client connects to our MCP server, gets the available tools, and makes them available to an OpenAI model. When a query is processed, the LLM can decide to call the get_knowledge_base tool. The client then executes that tool via the MCP session, sends the result back to the LLM, and gets a final, synthesized answer.

This flow is fundamental to how tool use works with LLMs, and MCP provides a standardized way to manage those tools.

Part 5: MCP vs. Function Calling

You've heard me mention this a couple of times: MCP adds nothing new in terms of capabilities. We've been able to do this for years with standard function calling. This short example highlights that.

# simple_tool_call.py
import os
from openai import OpenAI

def get_knowledge_base():
    """Retrieves the company knowledge base."""
    return "Employees get 20 days of paid vacation per year."

# ... (rest of the OpenAI tool-calling logic) ...

Here, the tool is defined directly in the same file. The only distinction is that instead of getting tools from an MCP server, we have them locally.

This should help you answer the question: "Should I migrate all my existing AI projects to MCP?" The answer is most likely no. If you're already using function calling and it's working, adding MCP will just add complexity initially. However, for new projects that rely heavily on tools, it might make sense to use MCP standards.

Part 6: Running MCP Servers with Docker

In the real world, the most practical way to use MCP servers is to deploy them somewhere they can be reused by other applications. Docker is perfect for this.

Here's a simple Dockerfile to containerize our server: ```dockerfile FROM python:3.11-slim

WORKDIR /app

COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt

COPY . .

Ensure the server listens on all interfaces within the container

CMD ["python", "-c", "from server import mcp; mcp.run(host='0.0.0.0', transport='sse')"] ```

You can build and run this Docker container: ```bash

Build the image

docker build -t mcp-server .

Run the container, mapping the port

docker run -p 8050:8050 mcp-server `` Now your server is running in a Docker container, and your client can connect to it onlocalhost:8050` just as before. You could deploy this container to a cloud provider and connect to it remotely from any application.

Part 7: Life Cycle Management

Finally, a brief note on lifecycle management. When building real applications that connect to various data sources like databases, it's important to properly manage your connections.

The MCP Python SDK provides a lifespan object for this. ```python from contextlib import asynccontextmanager

@asynccontextmanager async def lifespan(mcp): # Code to run on startup (e.g., connect to DB) print("Connecting to database...") yield # Code to run on shutdown (e.g., disconnect from DB) print("Disconnecting from database...")

mcp = FastMCP("my-server", lifespan=lifespan) ``` This is useful for ensuring connections are gracefully shut down. While you don't need to worry about this for simple examples, it's crucial for production systems where multiple servers and clients are interacting with stateful services.

Conclusion

I hope that by now you have a solid understanding of what MCP is and how you can use it as a Python developer—not just to integrate with desktop apps, but to build your own backends for AI systems and agents.

This was a deep dive, and to fully understand it, you'll likely need to experiment with the examples, adjust them, and create your own. Good luck!

Join the 10xdev Community

Subscribe and get 8+ free PDFs that contain detailed roadmaps with recommended learning periods for each programming language or field, along with links to free resources such as books, YouTube tutorials, and courses with certificates.

Recommended For You

Up Next