django-mcp adds MCP tool hosting to Django.
The Model Context Protocol (MCP) specification is relatively new and has been changing rapidly. This library provides an abstraction layer between Django and the upstream modelcontextprotocol/python-sdk as well as utility functions and decorators to simplify development of MCP services in Django applications.
Available on PyPI:
pip install django-mcp
Add 'django_mcp'
to your INSTALLED_APPS
setting like this:
# settings.py
INSTALLED_APPS = [
...
'django_mcp',
]
To use this library, you need to mount the MCP ASGI application to a route in your existing Django ASGI application.
First, configure your Django ASGI application entrypoint asgi.py
. Use mount_mcp_server
to mount the MCP server using Django-style URL path parameters. These URL path parameters will be available in the MCP Context object to any @mcp.tool
decorated functions.
# asgi.py
import os
import django
from django.core.asgi import get_asgi_application
# new import
from django_mcp import mount_mcp_server
# configure settings module path
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'my_project.settings')
# initialize django
django.setup()
# get the django http application
django_http_app = get_asgi_application()
# Mount MCP server dynamically using a URL parameter (e.g., user_uuid)
application = mount_mcp_server(django_http_app=django_http_app, mcp_base_path='/mcp/<slug:user_uuid>')
# for django-channels ASGI:
# from channels.routing import ProtocolTypeRouter
# application = ProtocolTypeRouter({
# "http": mount_mcp_server(django_http_app=django_http_app, mcp_base_path='/mcp/<slug:user_uuid>')
# })
Alternatively, if you don't need dynamic mounting, you can provide a static path:
# Simpler setup with a static path
application = mount_mcp_server(django_http_app=django_http_app, mcp_base_path='/mcp')
To start your server:
uvicorn my_project.asgi:application --host 0.0.0.0 --port 8000
Now the mcp_app
FastMCP object can be accessed in your project files with the same interface as defined in the upstream modelcontextprotocol/python-sdk SDK.
This library exports mcp_app
which corresponds to the upstream modelcontextprotocol/python-sdk FastMCP
object instance. You can use any of the upstream API decorators like @mcp_app.tool
to define your tools, prompts, resources, etc.
from django_mcp import mcp_app as mcp
@mcp.tool()
def add(a: int, b: int) -> int:
"""Add two numbers"""
return a + b
@mcp.resource("greeting://{name}")
def get_greeting(name: str) -> str:
"""Get a personalized greeting"""
return f"Hello, {name}!"
As shown in the ASGI setup section, mount_mcp_server
allows you to define Django-style URL path parameters (e.g., /mcp/<slug:user_uuid>
). These parameters are captured and made available within your tool functions via the path_params
attribute on the Context
object.
# Example demonstrating access to path parameters (e.g., user_uuid from /mcp/<slug:user_uuid>)
from mcp.server.fastmcp import Context
# Assuming User model exists
# from my_app.models import User
@mcp.tool()
async def get_user_info_from_path(ctx: Context) -> str:
"""Retrieves user info based on user_uuid from the URL path."""
# Access path parameters captured during mounting
path_params = getattr(ctx, 'path_params', {})
user_uuid = path_params.get('user_uuid', None)
if not user_uuid:
await ctx.error("User UUID not found in path parameters.")
return "Error: User UUID missing."
# Example: Fetch user from database (requires async ORM or sync_to_async)
# try:
# user = await User.objects.aget(uuid=user_uuid)
# return f"User found: {user.username}"
# except User.DoesNotExist:
# await ctx.warning(f"User with UUID {user_uuid} not found.")
# return f"Error: User {user_uuid} not found."
# Simplified example returning the uuid
await ctx.info(f"Retrieved user_uuid: {user_uuid}")
return f"Retrieved user_uuid: {user_uuid}"
This library allows customization through Django settings. The following settings can be defined in your project's settings.py
:
key | description | default |
---|---|---|
MCP_LOG_LEVEL |
Controls the MCP logging level | 'INFO' |
MCP_LOG_TOOL_REGISTRATION |
Controls whether tool registration is logged at startup | True |
MCP_LOG_TOOL_DESCRIPTIONS |
Controls whether tool descriptions are also logged | False |
MCP_SERVER_INSTRUCTIONS |
Sets the instructions provided by the MCP server | 'Provides MCP tools' |
MCP_SERVER_TITLE |
Sets the title of the MCP server | 'MCP Server' |
MCP_SERVER_VERSION |
Sets the version of the MCP server | '0.1.0' |
MCP_DIRS |
Additional search paths to load MCP modules | [] |
MCP_PATCH_SDK_TOOL_LOGGING |
Adds debug and exception logging to @tool decorator | True |
MCP_PATCH_SDK_GET_CONTEXT |
Adds URL path parameters to @tool Context object | True |
If a setting is not found in your project's settings.py
, the default value will be used.
django-mcp
provides several ways to log the execution of your MCP tools:
- Automatic Logging (Default):
By default,
django-mcp
automatically applies basic logging to all functions decorated with@mcp_app.tool()
. This is controlled by thesettings.MCP_PATCH_SDK_TOOL_LOGGING
setting which defaults toTrue
. This logging includes:- DEBUG level message upon tool entry.
- DEBUG level message upon successful tool exit, including the return value.
- WARNING level message if the tool raises an exception, including the exception details and traceback.
It's important to note that the standard @tool
decorator provided by the underlying mcp-python-sdk
does not, by itself, log exceptions raised within the tool function to the server's standard output or error streams. Exceptions are typically just passed back to the client. However, when settings.MCP_PATCH_SDK_TOOL_LOGGING
is enabled in django-mcp
(the default), the enhanced decorator applied by django-mcp
does intercept these exceptions, logs the details and traceback to the configured Django logger (visible in the server's console/stderr) at the WARNING level, and then allows the error to be passed back to the client as usual.
-
Manual Decorator Logging: If you disable
settings.MCP_PATCH_SDK_TOOL_LOGGING = False
in your Django settings, you can still apply the same logging behavior to specific tools using the@log_mcp_tool_calls
decorator. This decorator must be placed below the@mcp_app.tool()
decorator:from django_mcp import mcp_app as mcp from django_mcp.decorators import log_mcp_tool_calls # Import the decorator @mcp.tool() @log_mcp_tool_calls # Apply below @mcp.tool() def add(a: int, b: int) -> int: """Add two numbers""" return a + b
For more control over client-side logging within your tool's execution, or to report progress, you can request the Context
object provided by the underlying MCP python-sdk
. Add a parameter type-hinted as Context
to your tool function's signature. The Context
object provides methods like ctx.info()
, ctx.debug()
, ctx.warning()
, ctx.error()
, and ctx.report_progress()
which send structured messages back to the MCP client.
```python
from django_mcp import mcp_app
from mcp.server.fastmcp import Context # Import Context from the SDK
import asyncio # Added missing import for example
@mcp_app.tool()
async def a_very_long_task(input_data: str, ctx: Context):
await ctx.info("Starting a long task task...")
for i in range(10):
await ctx.report_progress(i*10, 100) # Report progress
await asyncio.sleep(3) # Sleep for 3 seconds
await ctx.info('... doing work...')
await ctx.info("Long task finished.")
return "Success"
```
When writing asynchronous MCP tools (using async def
) that interact with the Django ORM (version 4.1 or later), you should use the native asynchronous ORM methods provided by Django. For example, use await YourModel.objects.aget(pk=...)
instead of YourModel.objects.get(pk=...)
, and await YourModel.objects.acreate(...)
instead of YourModel.objects.create(...)
. Refer to the official Django documentation on asynchronous support for a complete list of async ORM methods and usage details.
For synchronous functions or operations that need to be called from an asynchronous context (like interacting with synchronous third-party libraries or performing operations that require a synchronous transaction block), use the sync_to_async
adapter from asgiref.sync
. For example:
from asgiref.sync import sync_to_async
from django_mcp import mcp_app
# Assuming Counter is a Django model with a sync method 'increment_sync'
from .models import Counter
@mcp_app.tool()
async def increment_counter_tool(counter_id: int) -> None:
"""Increment a counter, demonstrating sync_to_async"""
try:
counter = await Counter.objects.aget(pk=counter_id)
# Assume counter.increment_sync() is a synchronous method
await sync_to_async(counter.increment_sync)()
except Counter.DoesNotExist:
# Handle error appropriately
pass
This library includes a convenient management command to run the MCP Inspector tool against your Django application.
Start the inspector by running the following command in your project's root directory (where manage.py
is located):
python manage.py mcp_inspector [url]
Replace [url]
with the URL of your running MCP server, typically http://localhost:8000/mcp/sse
. If you omit the URL, it defaults to http://127.0.0.1:8000/mcp/sse
.
The command will start the inspector and output the URL (usually http://127.0.0.1:6274
) where you can access it in your web browser.
- Streamable HTTP transport
- Authentication and authorization
# Set up virtualenv (replace path)
export VIRTUAL_ENV=./.venv/django-mcp
uv venv --python 3.8 --link-mode copy ${VIRTUAL_ENV}
uv sync
This project is licensed un the MIT License.
By submitting a pull request, you agree that any contributions will be licensed under the MIT License, unless explicitly stated otherwise.