How to Build Custom MCP Servers in Python & TS [2026]
Learn how to build custom MCP servers using Python and TypeScript. Our definitive 2026 tutorial covers full Model Context Protocol development from concept to deployment.
Quick Answer
To build a custom MCP server, you implement the Model Context Protocol Server SDK (available via npm or pip). Define Tools (functions the AI can call) and Resources (data the AI can read). Test it locally by configuring Claude Desktop or Cursor to launch your script via stdio.
Before You Begin
Prerequisites Checklist
- Basic knowledge of TypeScript or Python 3.10+
- Node.js (v18+) or pip installed
- Claude Desktop App or Cursor IDE installed for testing
- Familiarity with standard JSON structures
Choosing Your Language: Python vs TypeScript
Anthropic essentially supports two primary first-party SDKs for MCP development. Here is how they break down in 2026.
TypeScript
@modelcontextprotocol/sdkBest for: Frontend devs, JavaScript shops, easy npm distribution
- Strong typing
- Zod integration
- Express/Fastify adapters
Python
mcp-serverBest for: Data scientists, ML engineers, backend heavy stacks
- Pydantic validation
- FastAPI adapters
- Native sync/async
Building a TypeScript Server
Below is a minimal example of a TypeScript MCP server that exposes a weather forecasting tool using Zod for robust input validation.
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
import { z } from "zod";
const server = new Server({
name: "weather-mcp",
version: "1.0.0",
});
// Define the arguments schema
const weatherSchema = z.object({
city: z.string(),
zipcode: z.string().optional()
});
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [{
name: "get_weather",
description: "Fetches current weather for a city",
// Convert zod schema to JSON schema format
inputSchema: zodToJsonSchema(weatherSchema)
}]
}));
server.setRequestHandler(CallToolRequestSchema, async (request) => {
if (request.params.name === "get_weather") {
const { city } = request.params.arguments;
// ... Implement fetch logic ...
return {
content: [{ type: "text", text: `It's sunny in ${city}!` }]
};
}
throw new Error("Tool not found");
});
const transport = new StdioServerTransport();
await server.connect(transport);Building a Python Server
If you prefer Python, you can utilize Pydantic models to quickly establish input validation structures. Here is the same concept constructed utilizing the official Python SDK.
import asyncio
from mcp.server import Server, NotificationOptions
from mcp.server.models import InitializationOptions
import mcp.server.stdio
import mcp.types as types
app = Server("weather-server")
@app.list_tools()
async def list_tools() -> list[types.Tool]:
return [
types.Tool(
name="get_weather",
description="Fetches current weather for a city",
inputSchema={
"type": "object",
"properties": {
"city": {"type": "string"}
},
"required": ["city"]
}
)
]
@app.call_tool()
async def call_tool(name: str, arguments: dict) -> list[types.TextContent]:
if name == "get_weather":
city = arguments.get("city")
return [types.TextContent(
type="text",
text=f"It's sunny in {city}!"
)]
async def main():
async with mcp.server.stdio.stdio_server() as (read, write):
await app.run(read, write, InitializationOptions())
if __name__ == "__main__":
asyncio.run(main())✅ Do:
- • Handle all internal errors gracefully so you do not crash the `stdio` pipe.
- • Use Zod or Pydantic to strongly define what properties `inputSchema` expects.
- • Test your server using the official MCP Inspector terminal tool.
❌ Don't:
- • Don't use `console.log` or `print()` for debugging (it breaks the JSON-RPC stdio protocol). Use `console.error` instead.
- • Don't expose destructive database tools without proper user-in-the-loop safeguards.
Frequently Asked Questions
What language should I use to build an MCP server?
The official SDKs support TypeScript (JavaScript) and Python. Choose the language that best fits your existing stack. TypeScript is great for web APIs and rapid NPM distribution, while Python is ideal for data or ML tasks.
How does an MCP server communicate with Claude?
Most local MCP servers communicate via stdio (standard input/output). The Claude Desktop App spins up your script as a subprocess and sends JSON-RPC messages back and forth over stdin/stdout. That's why standard print statements can break communication.
Can I monetize a custom MCP server?
Yes, but to monetize securely, you should avoid distributing raw executable scripts locally. Instead, host your MCP server remotely (using SSE or HTTP/WebSockets transits) and require standard API keys for user authentication.
Make Your Tools Available to the World
Once you build a functional MCP server, share it with the developer community! Submit your repository to our index or skip coding entirely and use our visual creator.