Build Your First MCP Server: A Hands-On Tutorial

Build your first MCP server with Python in this beginner-friendly tutorial. Learn how to create MCP tools, run your server, test it with MCP Inspector, and understand how MCP works through practical examples.

Build your first MCP server with Python tutorial

Building an MCP server sounds complicated until you actually build one.

At its simplest, an MCP server is a small program that gives AI applications access to tools or data. A tool could calculate something, query a database, call an API, read a file, or trigger an action.

In this tutorial, we'll build a small MCP server in Python from scratch.

By the end, you'll know how to:

  • Create an MCP project

  • Build an MCP server with Python

  • Create your first MCP tool

  • Run the server locally

  • Test it with MCP Inspector

  • Understand how the client and server communicate

If MCP itself is completely new to you, read Model Context Protocol (MCP) Explained first.

We won't repeat the architecture and history here.


What We're Building

We'll build a simple Calculator MCP Server.

It will expose tools such as:

add_numbers(10, 20) → 30

multiply_numbers(5, 4) → 20

Nothing fancy.

The point is to understand the complete MCP development workflow before connecting databases, APIs, GitHub, or other real services.

Architecture

┌─────────────────────┐
│     MCP Client      │
│                     │
│ Claude / AI Client  │
└──────────┬──────────┘
           │
           │ MCP Request
           ▼
┌─────────────────────┐
│     MCP Server      │
│                     │
│  Calculator Server  │
└──────────┬──────────┘
           │
           ├── add_numbers()
           │
           └── multiply_numbers()
           │
           ▼
┌─────────────────────┐
│    Python Logic     │
└─────────────────────┘

Architecture diagram of an MCP server built with Python

1. Prerequisites

You don't need advanced Python knowledge.

You should know basic:

def hello():
    return "Hello"

You'll need:

ToolWhy we need itPythonWrite our serveruvManage the Python projectMCP Python SDKBuild the MCP serverNode.jsRun MCP InspectorVS CodeEdit the project

Check Python:

python --version

Check Node.js:

node --version

If you don't have Python installed, follow our Python installation guide

For uv, use the official uv installation documentation rather than duplicating OS-specific installation instructions here.


2. Create the Project

Open your terminal.

Create a new project:

uv init mcp-calculator

Move into it:

cd mcp-calculator

Your project should initially look similar to:

mcp-calculator/
├── .python-version
├── README.md
├── main.py
└── pyproject.toml

We'll create our MCP server in:

server.py

Why are we using uv?

uv is a Python package and project manager.

You could use pip, but uv makes creating an isolated project and running its commands straightforward.

If you've never used it before, don't worry. We only need a few commands.


3. Install the MCP Python SDK

Install MCP:

uv add "mcp[cli]"

The CLI extra is useful because we'll use MCP's development tooling later.

If you're working with a traditional pip project, the equivalent is:

pip install "mcp[cli]"

The official MCP Python SDK provides the components required to build MCP servers and clients.

You can find the full SDK source and current version information in the official MCP Python SDK repository.


4. Create Your First MCP Server

Create:

server.py

Start with this:

from mcp.server.fastmcp import FastMCP

mcp = FastMCP("Calculator Server")


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

That's already the foundation of an MCP server.

Let's understand the two important pieces.

Creating the server

mcp = FastMCP("Calculator Server")

FastMCP gives us a high-level way to create an MCP server without manually implementing protocol messages.

Think of it like creating an application object in another framework.

For example, Flask developers might recognize:

app = Flask(__name__)

With MCP we're doing:

mcp = FastMCP("Calculator Server")

Different framework. Similar idea.

Starting the server

At the bottom:

mcp.run()

starts the server.

Our server doesn't have any useful capabilities yet.

Let's fix that.


5. Create Your First MCP Tool

Add this function:

@mcp.tool()
def add_numbers(a: int, b: int) -> int:
    """Add two numbers and return the result."""
    return a + b

Your complete server becomes:

from mcp.server.fastmcp import FastMCP

mcp = FastMCP("Calculator Server")


@mcp.tool()
def add_numbers(a: int, b: int) -> int:
    """Add two numbers and return the result."""
    return a + b


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

Look at this line:

@mcp.tool()

This is where an ordinary Python function becomes available as an MCP tool.

Without the decorator:

def add_numbers(a, b):

it's just a Python function.

With:

@mcp.tool()

the MCP server can expose that capability to connected MCP clients.


What Does the MCP Client See?

Your function contains more information than it might seem.

def add_numbers(a: int, b: int) -> int:

We defined:

a → integer
b → integer
return → integer

And we provided a description:

"""Add two numbers and return the result."""

The SDK can use this information when describing the tool to clients.

Conceptually, the client learns something like:

{
  "name": "add_numbers",
  "description": "Add two numbers and return the result.",
  "arguments": {
    "a": "integer",
    "b": "integer"
  }
}

You don't have to manually write that schema for this simple tool.

The SDK generates the relevant tool schema from your function definition.


6. Run the MCP Server

Run:

uv run server.py

Your server is now running.

But there's an important difference from a normal web application.

If you start Spring Boot, you might expect:

http://localhost:8080

With a local stdio MCP server, there doesn't have to be a website or REST endpoint to open.

The client can communicate with the server process through standard input/output.

If transports are still confusing, read:

MCP Architecture: Clients, Servers and Transports

That's exactly the kind of explanation we should keep out of this tutorial so you can stay focused on building.


7. Test It with MCP Inspector

Now comes the useful part.

Instead of connecting an AI application immediately, we'll test our server with MCP Inspector.

Run:

uv run mcp dev server.py

MCP Inspector is the official interactive development tool for testing and debugging MCP servers.

It allows you to inspect:

Server
├── Tools
├── Resources
├── Prompts
└── Server information

Open the Inspector interface shown by the development command.

Select:

Tools

You should find:

add_numbers

Enter:

a = 10
b = 20

Run the tool.

Expected result:

30

Your first MCP tool works.


Image 2 — MCP Inspector

Type: UI-style educational illustration

Size: 1600 × 900

Show:

MCP Inspector

Tools
 └── add_numbers

Arguments
 a: 10
 b: 20

[ Run Tool ]

Result
30

MCP Inspector connected to a Python MCP server


8. Add Another Tool

Let's add multiplication.

@mcp.tool()
def multiply_numbers(a: int, b: int) -> int:
    """Multiply two numbers and return the result."""
    return a * b

Our server now exposes two tools:

Calculator Server

├── add_numbers
│
└── multiply_numbers

Restart the development server if necessary:

uv run mcp dev server.py

Now MCP Inspector should discover both tools.

Try:

a = 5
b = 8

Expected result:

4

Notice something important.

We didn't create:

POST /add
POST /multiply

We didn't manually parse request JSON either.

Our Python functions are being exposed through MCP.


9. What Actually Happened?

Here's the request lifecycle.

User / AI
    │
    ▼
MCP Client
    │
    │ discovers available tools
    ▼
MCP Server
    │
    ├── add_numbers
    └── multiply_numbers
             │
             ▼
       Python Function
             │
             ▼
          Result
             │
             ▼
        MCP Client

The sequence is roughly:

Client connects
      ↓
Server initializes
      ↓
Client discovers tools
      ↓
Server describes add_numbers
      ↓
Client calls add_numbers
      ↓
Server validates arguments
      ↓
Python function runs
      ↓
Result returns to client

UML Sequence Diagram

UML sequence diagram of an MCP client talking to an MCP server

MCP workflow

This is the core MCP workflow.

Once you understand this diagram, larger MCP servers become much easier to reason about.


10. Handle Errors

Let's create division.

@mcp.tool()
def divide_numbers(a: float, b: float) -> float:
    """Divide a number by another number."""

    if b == 0:
        raise ValueError("Cannot divide by zero")

    return a / b

Test:

a = 20
b = 4

Result:

5

Now try:

a = 20
b = 0

The operation should fail rather than returning an invalid result.

Why does this matter?

Today we're calculating numbers.

Tomorrow your tool might:

delete_file()
send_email()
create_order()
deploy_application()
update_database()

Input validation becomes much more important when tools have real side effects.


11. Connect an MCP Client

MCP Inspector proves that our server works.

The next step is connecting an actual MCP-compatible client.

The exact configuration depends on the client you're using, so we shouldn't fill this tutorial with configuration that may change.

Instead, keep the article focused:

Using Claude Desktop?

Read:

How to Connect an MCP Server to Claude Desktop

Using VS Code or another MCP client?

Read:

How to Connect MCP Servers to AI Clients

This keeps the tutorial shorter and gives us strong internal linking opportunities.

At a high level, the connection looks like:

┌───────────────┐
│  AI Client    │
└───────┬───────┘
        │
        │ MCP
        ▼
┌───────────────┐
│ Calculator    │
│ MCP Server    │
└───────┬───────┘
        │
        ├── add_numbers
        ├── multiply_numbers
        └── divide_numbers

12. Common MCP Errors

ModuleNotFoundError: No module named 'mcp'

The MCP SDK isn't installed in the Python environment running your server.

Run:

uv add "mcp[cli]"

uv command not found

Verify:

uv --version

If the command isn't available, follow the official uv installation documentation.


Tool doesn't appear

Make sure the decorator exists:

@mcp.tool()
def add_numbers(a: int, b: int) -> int:

Then restart the development server.


Inspector doesn't start

Check Node.js:

node --version

Then:

npx --version

MCP Inspector can be launched through npx, so Node.js needs to be available.


Server works manually but not from a client

Check your project path and command.

A configuration using a relative path may work from your terminal but fail when another application starts the server.

Try the command manually first.

If:

uv run server.py

doesn't work normally, debug that before troubleshooting the client.


13. Final Code

Our final beginner server looks like this:

from mcp.server.fastmcp import FastMCP

mcp = FastMCP("Calculator Server")


@mcp.tool()
def add_numbers(a: int, b: int) -> int:
    """Add two numbers and return the result."""
    return a + b


@mcp.tool()
def multiply_numbers(a: int, b: int) -> int:
    """Multiply two numbers and return the result."""
    return a * b


@mcp.tool()
def divide_numbers(a: float, b: float) -> float:
    """Divide one number by another."""

    if b == 0:
        raise ValueError("Cannot divide by zero")

    return a / b


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

That's enough to understand the basic structure of an MCP server.

You don't need 500 lines of code.

The important pattern is:

mcp = FastMCP("My Server")


@mcp.tool()
def my_tool():
    # Your application logic
    pass


mcp.run()

14. Try It Yourself

Don't just copy the tutorial.

Add one tool yourself:

calculate_percentage

It should accept:

value
total

Example:

value = 25
total = 200

Expected:

12.5

Bonus

Handle:

total = 0

without crashing unexpectedly.

If your tool appears in MCP Inspector and returns the correct value, you've understood the basic MCP server workflow.


What You Learned

You now know the complete beginner MCP development cycle:

Create Project
      ↓
Install MCP SDK
      ↓
Create FastMCP Server
      ↓
Create Tool
      ↓
Run Server
      ↓
Test with Inspector
      ↓
Connect Client

More importantly, you've seen that an MCP server isn't mysterious infrastructure.

At the beginner level, you're mostly writing normal Python functions and exposing selected capabilities through a standard protocol.

From here, don't build another calculator.

Connect something real.

A database.

A REST API.

GitHub.

A file system.

Or one of your own backend services.

That's where MCP becomes interesting.


Previous Lesson

← Model Context Protocol (MCP) Explained: Complete Developer Guide


What's Next?

Now that you've built and tested your first MCP server, the next step is to connect MCP to something more useful than a calculator.

In the next tutorial, you'll learn how to build a practical MCP client Build Your First MCP Client from Scratch


Continue Learning

Explore more MCP and AI development tutorials:


Have a Question?

If something isn't clear or your MCP server isn't working as expected, leave a comment below.

We'll do our best to explain it with simple words, practical examples, and code you can actually understand.

Happy Coding! 🚀


Follow TheTechLane

Want more developer tutorials, AI engineering guides, programming tips, and tech updates?

Follow TheTechLane on LinkedIn, X, Instagram, Threads, and Pinterest to stay connected and catch new tutorials when they go live.

Learn. Build. Share.

— TheTechLane