loading…
Search for a command to run...
loading…
A Django MCP server that exposes tools and resources to AI agents using simple decorators, with auto-discovery, type safety, and custom authentication.
A Django MCP server that exposes tools and resources to AI agents using simple decorators, with auto-discovery, type safety, and custom authentication.
Full MCP (Model Context Protocol) server implementation for Django. Expose tools and resources to AI agents with simple decorators.
@mcp_tool, @mcp_resource, @mcp_promptmcp_tools.py in all Django appspip install mcp-django-server
# settings.py
INSTALLED_APPS = [
...
'mcp_server',
]
# Optional configuration
MCP_SERVER_NAME = "My Django MCP Server"
MCP_SERVER_VERSION = "1.0.0"
# Auth backend (see Authentication section)
# MCP_AUTH_BACKEND = 'myapp.mcp.auth.TokenAuth'
# Public methods — skip auth (default: initialize + notifications/initialized)
# MCP_PUBLIC_METHODS = ['initialize', 'notifications/initialized']
# Rate limiting via Django cache (calls per period in seconds)
# MCP_RATE_LIMIT = {"calls": 100, "period": 60}
# urls.py
from django.urls import path, include
urlpatterns = [
...
path('', include('mcp_server.urls')),
]
Create mcp_tools.py in any Django app:
# myapp/mcp_tools.py
from mcp_server import mcp_tool, mcp_resource
from .models import Product
@mcp_tool(
name="search_products",
description="Search products by name and category"
)
def search_products(query: str, category: str = None, limit: int = 10):
"""Search products - automatically exposed via /mcp/ endpoint."""
qs = Product.objects.filter(name__icontains=query)
if category:
qs = qs.filter(category=category)
return [
{"id": p.id, "name": p.name, "price": str(p.price)}
for p in qs[:limit]
]
@mcp_resource(
uri="catalog://products/{id}",
name="Product",
description="Full product details",
mime_type="application/json"
)
def get_product(id: int):
"""Get product by ID - exposed as MCP resource."""
p = Product.objects.get(pk=id)
return {
"id": p.id,
"name": p.name,
"description": p.description,
"price": str(p.price)
}
# Initialize
curl -X POST http://localhost:8000/mcp/ \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","clientInfo":{"name":"test"}}}'
# List tools
curl -X POST http://localhost:8000/mcp/ \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}'
# Call tool
curl -X POST http://localhost:8000/mcp/ \
-d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"search_products","arguments":{"query":"laptop"}}}'
Set MCP_AUTH_BACKEND in settings.py to require token authentication on every MCP request.
# settings.py
MCP_AUTH_BACKEND = 'myapp.mcp.auth.TokenAuth'
The class must implement authenticate(token: str) -> User | None:
# myapp/mcp/auth.py
class TokenAuth:
def authenticate(self, token):
from django.contrib.auth import get_user_model
User = get_user_model()
try:
return User.objects.get(mcp_token=token)
except User.DoesNotExist:
return None
When configured, every request must include Authorization: Bearer <token>.
A missing or invalid token returns HTTP 401.
If your tool function declares a user parameter, the authenticated user is injected automatically. The user parameter is excluded from the MCP input schema (not visible to clients).
@mcp_tool(description="Get my listings")
def get_my_listings(user):
return list(Listing.objects.filter(owner=user).values())
Use condition= to expose a tool only when a predicate on the user is satisfied. The tool is hidden from tools/list and blocked in tools/call when the condition returns False.
@mcp_tool(
description="Publish a listing",
condition=lambda user: hasattr(user, 'seller_profile')
)
def publish_listing(user, listing_id: int):
...
Override MCPView methods to add custom logic without touching the core dispatch:
# myapp/views.py
from mcp_server.views import MCPView
import logging
logger = logging.getLogger(__name__)
class AuditedMCPView(MCPView):
def before_dispatch(self, method, user):
logger.info("MCP call: method=%s user=%s", method, getattr(user, 'pk', None))
def after_tool_call(self, user, tool_name, result, duration_ms):
logger.info("Tool %s completed in %.1fms for user %s", tool_name, duration_ms, user)
# urls.py
from myapp.views import AuditedMCPView
urlpatterns = [
path('mcp/', AuditedMCPView.as_view()),
]
Enable per-user (or per-IP for anonymous) rate limiting via Django's cache:
# settings.py
MCP_RATE_LIMIT = {"calls": 100, "period": 60} # 100 requests per 60 seconds
Returns HTTP 429 with a JSON-RPC error when the limit is exceeded.
Register a function as an MCP tool.
@mcp_tool(name="tool_name", description="Tool description")
def my_tool(param1: str, param2: int = 10):
return {"result": "value"}
Parameters:
name (str, optional): Tool name. Defaults to function name.description (str): Tool description for AI agents.condition (callable, optional): (user) -> bool. When set, the tool is only listed/callable when the condition is satisfied for the current user.Type Hints:
str, int, float, bool, list, dictuser parameter (if present) is injected by the auth layer and excluded from the schemaRegister a function as an MCP resource.
@mcp_resource(
uri="catalog://items/{id}",
name="Item",
description="Item details",
mime_type="application/json"
)
def get_item(id: int):
return {"id": id, "data": "..."}
Parameters:
uri (str): Resource URI template with {param} placeholdersname (str, optional): Resource name. Defaults to function name.description (str): Resource descriptionmime_type (str): MIME type. Default: "application/json"Register a function as an MCP prompt template.
@mcp_prompt(
name="analyze",
description="Analyze data",
arguments=[
{"name": "data_id", "description": "Data ID", "required": True}
]
)
def analyze_prompt(data_id: int):
return f"Analyze data with ID: {data_id}"
Once installed, your Django app exposes:
POST /mcp/ - Main MCP JSON-RPC 2.0 endpointSupported methods:
initialize - Initialize MCP sessiontools/list - List all registered toolstools/call - Execute a toolresources/list - List all resourcesresources/read - Read a resourceprompts/list - List all promptsprompts/get - Get a promptIf you have django-mcp-discovery installed, this package automatically updates the /.well-known/mcp-server manifest with registered tools.
@mcp_tool(description="Get user by email")
def get_user(email: str):
from django.contrib.auth import get_user_model
User = get_user_model()
user = User.objects.get(email=email)
return {
"id": user.id,
"email": user.email,
"name": user.get_full_name()
}
@mcp_tool(description="Get weather forecast")
def get_weather(city: str):
import requests
response = requests.get(f"https://api.weather.com/{city}")
return response.json()
@mcp_resource(
uri="files://{path}",
description="Read file content",
mime_type="text/plain"
)
def read_file(path: str):
with open(path, 'r') as f:
return f.read()
MIT
Выполни в терминале:
claude mcp add mcp-django-server -- npx Безопасность
Низкий рискАвтоматическая эвристика по публичным данным — не гарантия безопасности.