MCP SDK: TypeScript vs Python Comparison
Compare the MCP TypeScript and Python SDKs. Learn the differences, strengths, and when to use each for building MCP servers and clients.
The Model Context Protocol offers official SDKs in both TypeScript and Python. While both implement the same protocol, they have different strengths and idioms. This guide helps you choose the right SDK for your project.
New to MCP? Start with our introduction to the Model Context Protocol.
Quick Comparison
| Aspect | TypeScript SDK | Python SDK |
|--------|---------------|------------|
| Async Model | Promises/async-await | asyncio |
| Validation | Zod | Pydantic |
| Type Safety | Excellent | Good (with type hints) |
| Ecosystem | npm | pip/PyPI |
| Best For | Web services, Claude Desktop | Data science, ML pipelines |
The TypeScript SDK
The TypeScript SDK is the reference implementation and typically receives new features first.
Installation
npm install @modelcontextprotocol/sdk
Basic Server Example
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: "ts-example",
version: "1.0.0",
});
// Define a tool with Zod schema
server.tool(
"get_weather",
"Get weather for a city",
{
city: z.string().describe("City name"),
units: z.enum(["celsius", "fahrenheit"]).default("celsius"),
},
async ({ city, units }) => {
const weather = await fetchWeather(city, units);
return {
content: [
{
type: "text",
text: Weather in ${city}: ${weather.temp}°${units === "celsius" ? "C" : "F"},
},
],
};
}
);
// Define a resource
server.resource(
"config",
"config://settings",
async (uri) => ({
contents: [
{
uri: uri.href,
mimeType: "application/json",
text: JSON.stringify({ version: "1.0.0" }),
},
],
})
);
// Start server
const transport = new StdioServerTransport();
await server.connect(transport);
TypeScript Strengths
1. Superior Type Safety
The TypeScript SDK leverages the type system fully:
// Tool inputs are fully typed
server.tool(
"create_user",
"Create a new user",
{
name: z.string(),
email: z.string().email(),
age: z.number().min(0).max(150),
},
async ({ name, email, age }) => {
// name: string, email: string, age: number
// TypeScript knows the types!
const user = await createUser({ name, email, age });
return { content: [{ type: "text", text: Created user ${user.id} }] };
}
);
2. Zod Integration
Zod provides powerful runtime validation:
const UserSchema = z.object({
name: z.string().min(1).max(100),
email: z.string().email(),
preferences: z.object({
theme: z.enum(["light", "dark"]),
notifications: z.boolean(),
}).optional(),
});server.tool(
"update_user",
"Update user preferences",
{ user: UserSchema },
async ({ user }) => {
// user is fully validated and typed
}
);
3. Excellent Async/Await
server.tool(
"fetch_all",
"Fetch data from multiple sources",
{ sources: z.array(z.string()) },
async ({ sources }) => {
// Parallel async operations
const results = await Promise.all(
sources.map(source => fetchData(source))
);
return {
content: [{ type: "text", text: JSON.stringify(results) }],
};
}
);
For a full TypeScript tutorial, see How to Build Your First MCP Server.
The Python SDK
The Python SDK brings MCP to the data science and ML ecosystem.
Installation
pip install mcp
Basic Server Example
from mcp.server import Server
from mcp.server.stdio import stdio_server
from mcp.types import TextContent, Tool
from pydantic import BaseModel, Field
import asyncioDefine input models with Pydantic
class WeatherInput(BaseModel):
city: str = Field(description="City name")
units: str = Field(default="celsius", description="Temperature units")Create server
server = Server("python-example")@server.list_tools()
async def list_tools():
return [
Tool(
name="get_weather",
description="Get weather for a city",
inputSchema=WeatherInput.model_json_schema(),
)
]
@server.call_tool()
async def call_tool(name: str, arguments: dict):
if name == "get_weather":
input_data = WeatherInput(arguments)
weather = await fetch_weather(input_data.city, input_data.units)
return [
TextContent(
type="text",
text=f"Weather in {input_data.city}: {weather['temp']}°"
)
]
raise ValueError(f"Unknown tool: {name}")
async def main():
async with stdio_server() as (read_stream, write_stream):
await server.run(read_stream, write_stream)
if __name__ == "__main__":
asyncio.run(main())
Python Strengths
1. Data Science Integration
Python excels when your MCP server needs ML libraries:
import pandas as pd
import numpy as np
from sklearn.linear_model import LinearRegression@server.call_tool()
async def call_tool(name: str, arguments: dict):
if name == "analyze_data":
# Load and process data with pandas
df = pd.read_csv(arguments["file_path"])
# Run ML models
model = LinearRegression()
model.fit(df[["x"]], df["y"])
return [TextContent(
type="text",
text=f"R² score: {model.score(df[['x']], df['y']):.4f}"
)]
2. Pydantic Validation
Pydantic provides similar validation to Zod:
from pydantic import BaseModel, Field, field_validator
from typing import Optional, Listclass AnalysisInput(BaseModel):
data: List[float] = Field(min_length=1)
method: str = Field(pattern="^(mean|median|std)$")
precision: Optional[int] = Field(default=2, ge=0, le=10)
@field_validator("data")
@classmethod
def validate_data(cls, v):
if any(x < 0 for x in v):
raise ValueError("All values must be non-negative")
return v
3. Native Async with asyncio
import aiohttp
import asyncioasync def fetch_multiple(urls: List[str]) -> List[dict]:
async with aiohttp.ClientSession() as session:
tasks = [fetch_url(session, url) for url in urls]
return await asyncio.gather(*tasks)
@server.call_tool()
async def call_tool(name: str, arguments: dict):
if name == "fetch_urls":
results = await fetch_multiple(arguments["urls"])
return [TextContent(type="text", text=str(results))]
For a complete Python guide, see Getting Started with MCP in Python.
Side-by-Side Code Comparison
Tool Definition
TypeScript:
server.tool(
"search",
"Search the database",
{
query: z.string(),
limit: z.number().default(10),
},
async ({ query, limit }) => {
const results = await db.search(query, limit);
return {
content: [{ type: "text", text: JSON.stringify(results) }],
};
}
);
Python:
class SearchInput(BaseModel):
query: str
limit: int = 10@server.list_tools()
async def list_tools():
return [
Tool(
name="search",
description="Search the database",
inputSchema=SearchInput.model_json_schema(),
)
]
@server.call_tool()
async def call_tool(name: str, arguments: dict):
if name == "search":
input_data = SearchInput(arguments)
results = await db.search(input_data.query, input_data.limit)
return [TextContent(type="text", text=json.dumps(results))]
Error Handling
TypeScript:
server.tool(
"risky_operation",
"Might fail",
{ input: z.string() },
async ({ input }) => {
try {
const result = await riskyOperation(input);
return {
content: [{ type: "text", text: result }],
};
} catch (error) {
return {
content: [{ type: "text", text: Error: ${error.message} }],
isError: true,
};
}
}
);
Python:
@server.call_tool()
async def call_tool(name: str, arguments: dict):
if name == "risky_operation":
try:
result = await risky_operation(arguments["input"])
return [TextContent(type="text", text=result)]
except Exception as e:
return [TextContent(type="text", text=f"Error: {str(e)}")]
When to Use Each SDK
Choose TypeScript When:
Choose Python When:
Migration Between SDKs
The protocol is the same, so you can migrate between SDKs if needed:
TypeScript to Python
1. Convert Zod schemas to Pydantic models
2. Replace server.tool() with decorator pattern
3. Update async patterns (Promise.all → asyncio.gather)
4. Adjust imports and project structure
Python to TypeScript
1. Convert Pydantic models to Zod schemas
2. Replace decorator pattern with server.tool()
3. Update async patterns
4. Add TypeScript types as needed
Hybrid Approach
You can run multiple MCP servers in different languages:
{
"mcpServers": {
"web-tools": {
"command": "node",
"args": ["typescript-server.js"]
},
"ml-tools": {
"command": "python",
"args": ["python_server.py"]
}
}
}
This lets you use each language where it excels.
Performance Considerations
Both SDKs perform similarly for typical use cases. The bottleneck is usually:
If you need extreme performance, both support async operations well.
Conclusion
Both SDKs are production-ready and implement the full MCP specification. Your choice should be driven by:
1. Team expertise: Use what your team knows
2. Ecosystem needs: Python for ML, TypeScript for web
3. Existing codebase: Wrap what you already have
4. Personal preference: Both are excellent choices
You can always start with one and migrate later — the protocol ensures compatibility.
Ready to start? Follow our tutorials for TypeScript or Python.