← Back to Articles
TypeScriptPythonSDKComparisonTutorial

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.

By Web MCP GuideFebruary 14, 20267 min read


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 asyncio

Define 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, List

class 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 asyncio

async 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:


  • Building web services: TypeScript/Node.js excels for HTTP servers

  • Maximum type safety: Catch more bugs at compile time

  • Claude Desktop focus: TypeScript servers integrate seamlessly

  • Team uses JavaScript/TypeScript: Leverage existing skills

  • Need fastest updates: TypeScript SDK is often updated first
  • Choose Python When:


  • Data science workflows: Pandas, NumPy, scikit-learn integration

  • ML model integration: TensorFlow, PyTorch, Hugging Face

  • Existing Python codebase: Wrap existing functionality

  • Team expertise: Python developers feel at home

  • Scripting simplicity: Less boilerplate for simple tools
  • 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:

  • Network requests to external services

  • Database queries

  • File I/O
  • 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.