Module 5: MCP Server in Python
Module 5: MCP Server in Python
Module 5: MCP Server in Python
Capsule description
You've just built a complete MCP server in TypeScript. You implemented tools with Zod, resources with URIs, configured transports, and saw Claude Code use your server as if it were a native tool. Now the question is: can I do the same in Python?
The short answer: yes, and in many cases it's more elegant.
The Python SDK for MCP takes a different approach from TypeScript. Where TypeScript uses classes and methods, Python uses decorators. Where TypeScript validates with Zod, Python validates with Pydantic and native type hints. Where TypeScript requires explicit type configuration, Python infers a lot from the type system.
This module isn't "the TypeScript module translated to Python." It's a module that leverages what Python does well — expressive decorators, clear type hints, mature asyncio — to build MCP servers that feel native to the language.
Where are we?
Context within the guide
Phase 1: MCP Fundamentals (Modules 1-3)
✅ Module 1: What MCP is and why it matters
✅ Module 2: Host-Client-Server Architecture
✅ Module 3: Three Primitives (Resources, Tools, Prompts)
Phase 2: Build MCP Servers (Modules 4-6)
✅ Module 4: MCP Server in TypeScript
→ Module 5: MCP Server in Python (YOU ARE HERE)
○ Module 6: MCP Apps and Interactive UI
Phase 3: Production (Modules 7-8)
○ Module 7: Testing, Debugging, and Integration
○ Module 8: Project — Real MCP Server
What you already know
From the previous modules you bring:
- MCP fundamentals — the protocol, the Host-Client-Server architecture, the request flow
- Three primitives — Resources (data), Tools (actions), Prompts (templates)
- A complete MCP server in TypeScript — tools with Zod, resources with URIs, stdio/HTTP transports
- Hands-on experience — you connected your server to Claude Code and used it end-to-end
What's missing
You know how to build in TypeScript, but Python is probably your main language. You need to:
- Understand the Python SDK and its philosophy (decorators, not classes)
- Use Pydantic instead of Zod for validation
- Handle async patterns in Python (asyncio, context managers)
- Build a Python MCP server that connects with an external REST API
Why Python for MCP?
The decision isn't "Python vs TypeScript"
You're not picking a side. You're adding a tool to your arsenal. The decision of which language to use for a specific MCP server depends on the context:
When to choose Python?
├── Your existing stack is Python
├── You need ML/data science libraries (pandas, numpy, scikit-learn)
├── The server processes data with tools from the Python ecosystem
├── Your team is stronger in Python
├── You need to integrate with Python frameworks (Django, FastAPI, Flask)
└── The MCP server wraps a service you already have in Python
When to choose TypeScript?
├── Your existing stack is Node.js/TypeScript
├── You want the most mature SDK (TS was first)
├── There are more reference MCP servers in TypeScript
├── You need the npm ecosystem
├── Your team is stronger in TypeScript
└── You need SSE/HTTP transport with the most tested implementation
Concrete advantages of the Python SDK
1. Decorators as the main interface
In TypeScript, you register tools with method calls:
server.tool("greet", "Greets the user", {
name: z.string(),
}, async ({ name }) => {
return { content: [{ type: "text", text: `Hello, ${name}!` }] };
});
In Python, you use decorators — more concise and Pythonic:
@mcp.tool()
async def greet(name: str) -> str:
"""Greets the user."""
return f"Hello, {name}!"
Less code, same functionality. The decorator extracts the tool's name from the function, the description from the docstring, and the input types from the type hint.
2. Pydantic instead of Zod
TypeScript uses Zod to define validation schemas:
const UserSchema = z.object({
name: z.string().min(1),
email: z.string().email(),
age: z.number().int().positive().optional(),
});
Python uses Pydantic — which many developers already know from FastAPI:
from pydantic import BaseModel, Field
class User(BaseModel):
name: str = Field(min_length=1)
email: str = Field(pattern=r'^[\w\.-]+@[\w\.-]+\.\w+$')
age: int | None = Field(default=None, gt=0)
If you've already used FastAPI, Pydantic is familiar to you. Python's type hints make the code more readable.
3. Native type hints
Python 3.10+ has an expressive type system that the SDK takes advantage of:
@mcp.tool()
async def search_files(
directory: str,
pattern: str,
max_results: int = 10,
include_hidden: bool = False
) -> str:
"""Searches files in a directory that match a pattern."""
...
The SDK infers the JSON schema automatically from the type hints. You don't need to define the schema separately.
4. FastMCP: the helper that simplifies everything
The Python SDK includes FastMCP, a high-level helper that reduces boilerplate:
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("my-server")
@mcp.tool()
async def my_tool(param: str) -> str:
"""Tool description."""
return f"Result: {param}"
if __name__ == "__main__":
mcp.run()
Compare this with the TypeScript setup — FastMCP handles the transport, the initialization, and the event loop for you.
Direct comparison: TypeScript vs Python
Comparison table
| Aspect | TypeScript SDK | Python SDK |
|---|---|---|
| Style | Classes and methods | Decorators |
| Validation | Zod schemas | Pydantic + type hints |
| Async | Promises/async-await | asyncio/async-await |
| Setup | npm + tsconfig + build | pip + virtualenv |
| Helper | McpServer class | FastMCP helper |
| Tool name | Explicit in .tool() | Inferred from the function name |
| Description | Explicit as a string | Inferred from the docstring |
| Schema | Defined with Zod | Inferred from type hints |
| Maturity | More mature SDK | Mature and stable SDK |
| Ecosystem | More reference servers | Growing rapidly |
Side-by-side example: the same server
TypeScript:
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
const server = new McpServer({
name: "calculator",
version: "1.0.0",
});
server.tool(
"add",
"Adds two numbers",
{ a: z.number(), b: z.number() },
async ({ a, b }) => ({
content: [{ type: "text", text: String(a + b) }],
})
);
server.tool(
"multiply",
"Multiplies two numbers",
{ a: z.number(), b: z.number() },
async ({ a, b }) => ({
content: [{ type: "text", text: String(a * b) }],
})
);
server.resource(
"history",
"calc://history",
{ description: "Operation history" },
async (uri) => ({
contents: [{ uri: uri.href, mimeType: "application/json", text: "[]" }],
})
);
const transport = new StdioServerTransport();
await server.connect(transport);
Python (equivalent):
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("calculator")
@mcp.tool()
async def add(a: float, b: float) -> str:
"""Adds two numbers."""
return str(a + b)
@mcp.tool()
async def multiply(a: float, b: float) -> str:
"""Multiplies two numbers."""
return str(a * b)
@mcp.resource("calc://history")
async def get_history() -> str:
"""Operation history."""
return "[]"
if __name__ == "__main__":
mcp.run()
The Python server has half the lines for the same functionality. It's not that TypeScript is bad — it's that Python is more concise for this kind of declarative code.
What does NOT change between languages
Regardless of the language you choose:
- The MCP protocol is the same — JSON-RPC 2.0 over transports
- The primitives are the same — Resources, Tools, Prompts
- The configuration in Claude Code is the same —
claude mcp add - The Host-Client-Server flow is the same — nothing changes at the architecture level
- MCP Inspector works the same — you debug the same way
What changes is the development ergonomics. The Python SDK is more concise; the TypeScript one is more explicit. Both produce MCP servers that behave identically from the host's perspective.
Module objective
By the end of this module, you'll be able to:
- ✅ Create an MCP project in Python from scratch with
pip install mcp[cli] - ✅ Use
FastMCPto configure a server with minimal boilerplate - ✅ Implement tools using the
@mcp.tool()decorator with type hints - ✅ Implement resources using the
@mcp.resource()decorator with URIs - ✅ Use Pydantic for validation of complex inputs
- ✅ Handle async patterns: async functions, context managers, asyncio
- ✅ Connect your Python MCP server to a real external REST API
- ✅ Decide with good judgment when to choose Python vs TypeScript for an MCP server
- ✅ Have a functional Python MCP server connected to Claude Code
Module roadmap
| Capsule | Topic | What you'll learn |
|---|---|---|
| 02 | Setup: MCP Python | Install the SDK, create the project structure, understand decorators and FastMCP |
| 03 | Tools and Resources in Python | Implement tools and resources with decorators, Pydantic for validation, differences from TS |
| 04 | Async Patterns in MCP | asyncio, async context managers, async generators, async error handling |
| 05 | Project: Python MCP Server | Complete server that connects with an external REST API, testing, connection with Claude Code |
Learning flow
The progression is deliberate:
- Setup (capsule 02) — development environment, dependencies, and your first Python server running. No friction.
- Tools and Resources (capsule 03) — the meat of the module. You implement primitives with the Pythonic style: decorators, type hints, Pydantic.
- Async patterns (capsule 04) — the SDK is async-first. You master asyncio in the context of MCP: connections to APIs, databases, files.
- Project (capsule 05) — everything converges into a Python MCP server that connects with a real REST API. End-to-end.
Each capsule builds on the previous one. You can't implement tools without the setup, and you can't connect APIs without mastering async.
What this module assumes
About your Python knowledge
This module doesn't teach Python. It assumes that:
- You write functions, classes, and modules in Python fluently
- You understand decorators (at least how to use them, if not how to create them)
- You've used type hints (
str,int,list[str],dict[str, Any]) - You've seen
async/await(you don't need to be an expert — we go deeper in capsule 04) - You've used
pipand virtual environments
About your MCP knowledge
It assumes you completed modules 1-4:
- You understand the MCP protocol, the architecture, and the three primitives
- You built an MCP server in TypeScript (module 4)
- You know how to configure MCP servers in Claude Code
- You've used MCP Inspector for debugging
If you jumped straight to module 5 without doing module 4, the module works — but you'll miss the comparisons with TypeScript that enrich the understanding.
Connection to the capstone project
This module's mini-project (capsule 05)
You're going to build a Python MCP server that:
- Connects with an external REST API (GitHub API or weather API)
- Exposes resources to query the API's data
- Exposes tools to execute actions through the API
- Exposes prompts to standardize common queries
- Includes robust error handling for network and API failures
- Connects to Claude Code and works end-to-end
Connection to module 8
The capstone project in module 8 asks you for a production-ready MCP server. If you choose Python (a completely valid option), this module gives you all the tools. The patterns you learn here — decorators, Pydantic, async patterns — apply directly to the final project.
What you add in module 8 that you don't cover here:
- Automated testing (module 7)
- Advanced error handling and retry logic
- Professional documentation
- Production configuration
Technical prerequisites
For this module you need:
- ✅ Python 3.10+ installed (
python --versionto check) - ✅ pip up to date (
pip install --upgrade pip) - ✅ Claude Code installed and working
- ✅ An editor with Python support (VS Code, Cursor, PyCharm)
- ✅ An internet connection (to install dependencies and connect APIs)
Quick verification:
python --version # Python 3.10+
pip --version # pip 23+
claude --version # Claude Code installed
If any fails, fix it before continuing. Capsule 02 covers the SDK installation, but Python and pip must be ready.
Boundaries: what is NOT covered in this module
- ❌ Advanced transports (HTTP/SSE) — Covered in module 4 with TypeScript; in Python it's configured similarly
- ❌ Automated testing — That comes in module 7
- ❌ MCP Apps and UI — That comes in module 6
- ❌ Deployment to production — Outside the scope of the guide
- ❌ Advanced Python (metaclasses, descriptors) — Not necessary for MCP
- ❌ An exhaustive comparison of the SDKs — We cover the key differences, not every detail
This module is hands-on construction in Python. You come in knowing MCP in TypeScript, you leave knowing MCP in Python.
Signs of success
By the end of this module, you'll know you succeeded if:
- ✅ You can create a Python MCP server from scratch with
FastMCP - ✅ You implement tools with
@mcp.tool()and type hints for validation - ✅ You implement resources with
@mcp.resource()and URIs - ✅ You handle async patterns without errors (connections, APIs, files)
- ✅ You have a Python MCP server connected to a real REST API
- ✅ Claude Code uses your Python server and it works end-to-end
- ✅ You can articulate when to choose Python vs TypeScript for an MCP server
- ✅ You feel prepared for the capstone project in module 8
Mindset for this module
"Use what you already know"
You're not learning MCP again — you already learned it in modules 1-4. You're learning to express what you already know in Python. The protocol is the same. The primitives are the same. What changes is the syntax and the tools.
"Python-native, not translated TypeScript"
You're going to feel the temptation to write Python that looks like TypeScript. Resist it. Python has its own style:
# ❌ TypeScript translated to Python
server.tool("greet", "Greets the user", {"name": str}, lambda args: f"Hello, {args['name']}")
# ✅ Native Python with decorators
@mcp.tool()
async def greet(name: str) -> str:
"""Greets the user."""
return f"Hello, {name}!"
The Python SDK is designed for you to write idiomatic Python. Take advantage of it.
"Async isn't optional"
The Python SDK is async-first. If you avoid async/await, you'll fight with the SDK instead of leveraging it. Capsule 04 is dedicated to async patterns — if async intimidates you, that capsule will give you the confidence you need.
Summary
- This module teaches you to build MCP servers in Python, using the official SDK
- Python uses decorators (
@mcp.tool(),@mcp.resource()) instead of classes - Pydantic replaces Zod for schema validation
- FastMCP is the helper that reduces boilerplate
- The module is hands-on: setup → tools/resources → async → project
- By the end you'll have a Python MCP server connected to a real REST API
- The MCP protocol is the same — what changes is the development ergonomics
Additional resources
- MCP Python SDK — Official SDK repository
- MCP Python SDK — FastMCP — Documentation for the FastMCP helper
- Pydantic Documentation — Validation and serialization in Python
- Python asyncio Documentation — Official asyncio reference
- MCP Specification — Protocol specification (language-independent)
- MCP TypeScript SDK — To compare with the TypeScript implementation
Next capsule: Setup — SDK installation, project structure, and your first Python MCP server running in less than 5 minutes.