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.

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 │
└─────────────────────┘

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 --versionCheck Node.js:
node --versionIf 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-calculatorMove into it:
cd mcp-calculatorYour project should initially look similar to:
mcp-calculator/
├── .python-version
├── README.md
├── main.py
└── pyproject.tomlWe'll create our MCP server in:
server.pyWhy 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 + bYour 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 → integerAnd 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.pyYour 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:8080With 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.pyMCP Inspector is the official interactive development tool for testing and debugging MCP servers.
It allows you to inspect:
Server
├── Tools
├── Resources
├── Prompts
└── Server informationOpen the Inspector interface shown by the development command.
Select:
ToolsYou should find:
add_numbersEnter:
a = 10
b = 20Run the tool.
Expected result:
30Your 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

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 * bOur server now exposes two tools:
Calculator Server
├── add_numbers
│
└── multiply_numbersRestart the development server if necessary:
uv run mcp dev server.pyNow MCP Inspector should discover both tools.
Try:
a = 5
b = 8Expected result:
4Notice something important.
We didn't create:
POST /add
POST /multiplyWe 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 ClientThe 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

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 / bTest:
a = 20
b = 4Result:
5Now try:
a = 20
b = 0The 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 --versionIf 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 --versionThen:
npx --versionMCP 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.pydoesn'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_percentageIt should accept:
value
totalExample:
value = 25
total = 200Expected:
12.5Bonus
Handle:
total = 0without 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:
MCP Tools Explained: Build and Expose Your Own Tools
MCP Resources Explained with Real Examples
MCP Prompts Explained: Reusable Prompt Workflows
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