Introduction
Claude AI, powered by Anthropic's advanced models like Opus, Sonnet, and Haiku, excels at reasoning, coding, and complex tasks. But to truly unlock its potential in real-world applications, you need to extend it with external tools. Enter Model Context Protocol (MCP) servers—lightweight HTTP servers that expose custom functions as callable tools for Claude.
MCP servers bridge Claude's native abilities with your data sources, APIs, and computations. Whether you're querying databases, scraping websites, or integrating third-party services, custom MCP servers make Claude more powerful and context-aware. In this guide, we'll build production-ready MCP servers using Python and Flask, deploy them, and integrate with Claude's ecosystem.
This is perfect for developers using Claude API, Claude Code CLI, or building AI agents.
What is MCP?
MCP is Anthropic's protocol for tool integration, allowing Claude to discover, call, and receive results from external functions dynamically. Key features:
- Tool Discovery: Claude queries
/mcp/toolsto list available tools with schemas. - Tool Execution: Claude sends calls to
/mcp/callwith JSON payloads. - Streaming Support: Real-time responses for long-running tasks.
- Stateful Sessions: Optional context persistence across calls.
Unlike generic APIs, MCP is optimized for LLMs: schemas use JSON Schema, calls are batched, and errors are structured. It's used in Claude Code, agents, and custom integrations.
Why Build Custom MCP Servers?
- Claude-Specific: Native support in Claude API—no adapters needed.
- Flexibility: Run anywhere (local, cloud, edge).
- Scalability: Handle enterprise loads with async Python.
- Security: Fine-grained auth and validation.
Real-world use cases:
- Engineering: Database queries, code execution sandboxes.
- Marketing: CRM lookups, analytics fetches.
- HR: Employee data retrieval, compliance checks.
Prerequisites
- Python 3.10+
- Familiarity with Flask/FastAPI (we'll use Flask for simplicity)
- Claude API key (from console.anthropic.com)
- Basic knowledge of JSON Schema
Install dependencies:
pip install flask requests anthropic pydantic
Step 1: Set Up Your MCP Server Skeleton
Create mcp_server.py:
import json
from flask import Flask, request, jsonify
from pydantic import BaseModel
app = Flask(__name__)
class ToolSchema(BaseModel):
name: str
description: str
inputSchema: dict
TOOLS = [] # Populate with your tools
@app.route('/mcp/tools', methods=['GET'])
def list_tools():
return jsonify([{
'name': tool.name,
'description': tool.description,
'inputSchema': tool.inputSchema
} for tool in TOOLS])
@app.route('/mcp/call', methods=['POST'])
def call_tool():
data = request.json
tool_name = data['name']
args = data['arguments']
# Execute tool logic here
result = {'content': 'Tool result'}
return jsonify(result)
if __name__ == '__main__():
app.run(debug=True, port=5000)
Run it: python mcp_server.py. Test with curl http://localhost:5000/mcp/tools.
Step 2: Implement Your First Tool - Calculator
Extend with a calculator tool. Define schema for addition.
Add to TOOLS:
calc_tool = ToolSchema(
name='calculator',
description='Add two numbers',
inputSchema={
'type': 'object',
'properties': {
'a': {'type': 'number'},
'b': {'type': 'number'}
},
'required': ['a', 'b']
}
)
TOOLS.append(calc_tool)
In /mcp/call:
if tool_name == 'calculator':
a = args['a']
b = args['b']
return jsonify({'content': str(a + b)})
Test:
curl -X POST http://localhost:5000/mcp/call \
-H "Content-Type: application/json" \
-d '{"name": "calculator", "arguments": {"a": 5, "b": 3}}'
Output: {"content": "8"}
Step 3: Advanced Tool - Weather Lookup
Integrate OpenWeatherMap (get free API key).
New tool:
import requests
weather_tool = ToolSchema(
name='get_weather',
description='Get current weather for a city',
inputSchema={
'type': 'object',
'properties': {
'city': {'type': 'string'}
},
'required': ['city']
}
)
TOOLS.append(weather_tool)
# In call_tool:
if tool_name == 'get_weather':
city = args['city']
api_key = 'YOUR_API_KEY'
url = f'https://api.openweathermap.org/data/2.5/weather?q={city}&appid={api_key}&units=metric'
resp = requests.get(url).json()
temp = resp['main']['temp']
return jsonify({'content': f"Temperature in {city}: {temp}°C"})
Claude can now fetch real-time data!
Step 4: Integrate with Claude API
Use Anthropic SDK to call your MCP server.
import anthropic
client = anthropic.Anthropic(api_key='your-claude-key')
message = client.messages.create(
model="claude-3-5-sonnet-20240620",
max_tokens=1024,
tools=[{
"type": "mcp_server",
"mcp_server_url": "http://localhost:5000"
}],
messages=[{"role": "user", "content": "What's 15 + 27? Also, weather in London?"}]
)
print(message.content)
Claude auto-discovers tools, plans calls, and incorporates results.
Step 5: Add Authentication and Error Handling
Secure your server:
from functools import wraps
API_KEYS = {'secret-key': True}
def auth(f):
@wraps(f)
def decorated(*args, **kwargs):
key = request.headers.get('X-API-Key')
if not API_KEYS.get(key):
return jsonify({'error': 'Unauthorized'}), 401
return f(*args, **kwargs)
return decorated
app.route('/mcp/tools', methods=['GET'])(auth(list_tools))
app.route('/mcp/call', methods=['POST'])(auth(call_tool))
Pass key: -H "X-API-Key: secret-key"
Handle errors:
try:
# tool logic
except Exception as e:
return jsonify({'error': str(e)}), 400
Step 6: Streaming and Async Support
For long tasks, stream responses:
from flask import Response
@app.route('/mcp/call', methods=['POST'])
def call_tool():
# ...
def generate():
yield json.dumps({'content': 'Starting...'}) + '\
'
# Simulate work
yield json.dumps({'content': 'Done!'}) + '\
'
return Response(generate(), mimetype='application/json')
Claude supports streaming MCP calls.
Step 7: Deployment
Local/Dev: flask run
Cloud:
-
Render.com: Free tier, deploy via Git.
# render.yaml services: - type: web name: claude-mcp env: python buildCommand: pip install -r requirements.txt startCommand: gunicorn mcp_server:app -
Vercel: Serverless with
vercel.json. -
AWS Lambda: Use Mangum for ASGI.
Expose public URL, update Claude prompt with it.
For Claude Code CLI: claude-code --mcp http://your-server.com
Best Practices
- Validation: Use Pydantic for inputs/outputs.
- Idempotency: Handle retries with unique IDs.
- Logging:
logging.info(f"Tool {tool_name} called with {args}") - Rate Limiting: Flask-Limiter.
- Schemas: Keep simple, descriptive.
- Testing: Unit tests for each tool.
Example test:
import unittest
from app import app
class TestMCP(unittest.TestCase):
def setUp(self):
self.client = app.test_client()
def test_calculator(self):
rv = self.client.post('/mcp/call', json={'name': 'calculator', 'arguments': {'a': 1, 'b': 2}})
self.assertEqual(rv.json['content'], '3')
if __name__ == '__main__':
unittest.main()
Advanced Topics
- Stateful Tools: Use Redis for session state.
- Multi-Tool Chaining: Claude orchestrates sequences.
- n8n/Zapier Integration: Webhook to MCP endpoint.
- Enterprise: VPC peering, IAM roles.
Compare with others:
| Feature | MCP | OpenAI Tools | LangChain |
|---|---|---|---|
| Claude Native | ✅ | ❌ | Adapter |
| Schema Auto | ✅ | Partial | Manual |
| Streaming | ✅ | ✅ | Plugin |
Conclusion
Custom MCP servers transform Claude from a smart assistant into a full-fledged agent platform. Start with the calculator example, iterate to your domain tools, and deploy to production. Experiment with Claude Opus for complex orchestration.
Resources:
- Anthropic Docs: MCP
- GitHub: this-server
Build yours today—share in comments!
(Word count: ~1450)
Stay ahead of the AI curve
The most important updates, news, and content — delivered in one weekly newsletter.