Building an MCP Server from Scratch 2026: Create Custom AI Tools with Python
A complete MCP server development tutorial that lets Claude and Cursor call your custom functions
Why Build Your Own MCP Server
While existing MCP server libraries are already rich, there are still many scenarios that require customization:
Development Environment Setup
bash
Install the MCP Python SDK
pip install mcpOr use uv
uv add mcp
The Simplest MCP Server
python
simple_server.py
from mcp.server import Server
from mcp.server.stdio import stdio_server
from mcp.types import Tool, TextContent
import mcp.types as typesCreate a server instance
app = Server("my-tools")Register the list of tools
@app.list_tools()
async def list_tools() -> list[Tool]:
return [
Tool(
name="get_weather",
description="Get weather information for a specified city",
inputSchema={
"type": "object",
"properties": {
"city": {"type": "string", "description": "City name"}
},
"required": ["city"]
}
),
Tool(
name="calculate_discount",
description="Calculate the price after discount",
inputSchema={
"type": "object",
"properties": {
"original_price": {"type": "number"},
"discount_percent": {"type": "number"}
},
"required": ["original_price", "discount_percent"]
}
)
]Implement tool logic
@app.call_tool()
async def call_tool(name: str, arguments: dict):
if name == "get_weather":
city = arguments["city"]
# In a real project, call the actual weather API
weather_data = {"city": city, "temp": "25°C", "condition": "Sunny"}
return [TextContent(type="text", text=str(weather_data))]
elif name == "calculate_discount":
original = arguments["original_price"]
discount = arguments["discount_percent"]
final_price = original * (1 - discount / 100)
return [TextContent(
type="text",
text=f"Original price {original} yuan, after {100-discount}% off: {final_price:.2f} yuan"
)]
raise ValueError(f"Unknown tool: {name}")Start the server (stdio mode)
if __name__ == "__main__":
import asyncio
asyncio.run(stdio_server(app))
Register with Claude Desktop
json
{
"mcpServers": {
"my-tools": {
"command": "python",
"args": ["/path/to/simple_server.py"]
}
}
}
Hands-on: Building a Database Query Tool
python
import sqlite3
from mcp.server import Server
from mcp.server.stdio import stdio_server
from mcp.types import Tool, TextContentapp = Server("db-tools")
DB_PATH = "./mydata.db"
@app.list_tools()
async def list_tools():
return [
Tool(
name="query_database",
description="Execute a SQL query (read-only) and return results",
inputSchema={
"type": "object",
"properties": {
"sql": {"type": "string", "description": "SELECT SQL statement"}
},
"required": ["sql"]
}
)
]
@app.call_tool()
async def call_tool(name: str, arguments: dict):
if name == "query_database":
sql = arguments["sql"]
# Security check: only allow SELECT
if not sql.strip().upper().startswith('SELECT'):
return [TextContent(type="text", text="Error: Only SELECT queries are allowed")]
conn = sqlite3.connect(DB_PATH)
cursor = conn.cursor()
cursor.execute(sql)
columns = [desc[0] for desc in cursor.description]
rows = cursor.fetchall()
conn.close()
result = f"Columns: {columns}\nResults ({len(rows)} rows):\n"
for row in rows[:20]: # Return at most 20 rows
result += str(row) + "\n"
return [TextContent(type="text", text=result)]
if __name__ == "__main__":
import asyncio
asyncio.run(stdio_server(app))
Debugging Tips
bash
Test if the server starts correctly
echo '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | python simple_server.pyUse MCP Inspector for visual testing
npx @modelcontextprotocol/inspector python simple_server.py
Publishing to the MCP Community
pip publishuvx your-package-nameAlso available in 中文.