Skip to main content

MCP server for Django REST Framework — auto-discovers DRF views and exposes them as MCP tools

Project description

django-rest-mcp

Turn the Django REST Framework API you already have into MCP tools, without rewriting a thing.

Why

You have a DRF API. You want an MCP client (Claude, an agent, your own tooling) to call it.

The usual way is to hand-write an MCP tool for every endpoint, re-describe your serializers as tool inputs, and re-check your permissions in a second place that quietly drifts from the real API.

This package skips that. Point it at your existing DRF router and every ViewSet action becomes an MCP tool, input types pulled straight from your serializers, every call running through your real permissions and querysets. One source of truth: your API. If curl can hit it, an MCP client can too.

It owns no models, no business logic, no views of its own. It is glue.

What you get

Register a BookViewSet under books and an MCP client sees six tools:

books_list   books_retrieve   books_create   books_update   books_partial_update   books_destroy

For each one:

  • Typed inputs, generated from the action's serializer, so the model knows exactly what fields to send.
  • Runs as the authenticated user, so your permission_classes, OAuth scopes, object-level permissions, and get_queryset() filtering all apply unchanged.
  • Returns whatever your API already returns.

Install

pip install django-rest-mcp

Python 3.12+, Django 5.1+, DRF 3.14+, mcp>=1.26, pydantic>=2.

How

You already have a router. Three lines:

# myapp/urls.py
from django.urls import path
from drf_mcp import DRFMCP
from myapp.urls import router          # your existing DefaultRouter

mcp = DRFMCP("myapp")
mcp.autodiscover(router)

urlpatterns = [path("mcp/", mcp.as_view()), ...]

That exposes every standard action on every registered ViewSet. That's it.

Pick what to expose

mcp.autodiscover(router, include=["books"])       # only these basenames
mcp.autodiscover(router, exclude=["internal"])     # all but these

# or register one action at a time, with a custom name + description:
mcp.register_view(BookViewSet, action="list", name="list_books",
                  description="Return all books the current user can read.")

Add OAuth (production shape)

Front it with django-oauth-toolkit plus a permission class, and serve the .well-known/ discovery endpoints so MCP clients can find your auth server:

from drf_mcp import (
    DRFMCP, IsOAuth2Authenticated,
    AuthorizationServerMetadataView, ProtectedResourceMetadataView,
)

mcp = DRFMCP("myapp")
mcp.autodiscover(router)

urlpatterns = [
    path("mcp/", mcp.as_view(permission_classes=[IsOAuth2Authenticated]), name="mcp"),
    path(".well-known/oauth-authorization-server", AuthorizationServerMetadataView.as_view()),
    path(".well-known/oauth-protected-resource",   ProtectedResourceMetadataView.as_view()),
]
pip install 'django-rest-mcp[oauth]'

Authentication

Each tool runs as the request's authenticated user. That works whether you authenticate with OAuth2 tokens, session/cookie auth, or a custom backend, so your permissions and querysets behave exactly as they do over HTTP.

IsOAuth2Authenticated accepts a request only if request.user is authenticated and request.auth looks like an OAuth2 token. Pair it with:

REST_FRAMEWORK = {
    "DEFAULT_AUTHENTICATION_CLASSES": [
        "oauth2_provider.contrib.rest_framework.OAuth2Authentication",
    ],
}

Reach the live request from inside a tool:

from drf_mcp import get_current_request

request = get_current_request()
request.user   # the authenticated user
request.auth   # the OAuth2 access token

Tool inputs

For write actions (create, update, partial_update) the input schema is built from the serializer (get_serializer_class(), falling back to serializer_class). Each writable field maps to a Python type:

DRF field Python type
CharField, EmailField, URLField, ... str
IntegerField int
FloatField, DecimalField float
BooleanField bool
ListField list
DictField dict
JSONField Any
nested Serializer typed submodel
Serializer(many=True) List[submodel]

required=False fields become Optional[...]; read-only and hidden fields are skipped. The generated model is named <Serializer>Input.

Tool descriptions

What the model reads as a tool's description comes from, in order:

  1. The description= you pass to register_view.
  2. The action method's docstring (only if defined on the ViewSet itself, not inherited from a mixin).
  3. The ViewSet's class docstring.
  4. A generated fallback like "List all Book".

Write docstrings to steer the model on when to use a tool:

class BookViewSet(viewsets.ModelViewSet):
    """Books in the catalogue."""

    def create(self, request):
        """Create a book. Call books_list first to avoid duplicates."""
        ...

Multi-tenant OAuth

For "one user belongs to many orgs, and each MCP connection binds to exactly one of them", the package ships drop-in replacements for django-oauth-toolkit's authorize and token views, a Dynamic Client Registration endpoint (RFC 7591), and a consent page with an org picker.

# settings.py
INSTALLED_APPS = [..., "oauth2_provider", "drf_mcp"]

DRF_MCP = {
    "RESOURCE_PATH": "/api/mcp/",
    "SCOPES": ["read:api", "create:api"],

    # Org picker on the consent page. Returns objects with `.id` and `.name`.
    "GET_USER_ORGS": "myapp.mcp_hooks.get_user_orgs",

    # Bind the issued token's Application to the org the user picked, so
    # `request.auth.application.organisation` resolves to that org.
    "GET_OR_CREATE_PER_ORG_APP": "myapp.mcp_hooks.get_or_create_per_org_app",

    # HTTPS hosts allowed to register a redirect_uri via DCR (loopback HTTP is
    # always allowed).
    "REGISTRATION_HTTPS_HOST_SUFFIXES": ["claude.ai", "anthropic.com"],
}
# urls.py
from drf_mcp import (
    DRFMCP, IsOAuth2Authenticated, MCPView,
    MCPAuthorizationView, MCPTokenView, StaticClientRegistrationView,
    AuthorizationServerMetadataView, ProtectedResourceMetadataView,
)

mcp = DRFMCP("myapi")
mcp.autodiscover(router)

class MyMCPView(MCPView):
    mcp_server = mcp
    permission_classes = [IsOAuth2Authenticated]

urlpatterns = [
    path("o/authorize/",  MCPAuthorizationView.as_view(), name="authorize"),
    path("o/token/",      MCPTokenView.as_view(),         name="token"),
    path("mcp/",          MyMCPView.as_view(),            name="mcp"),
    path("mcp/register/", StaticClientRegistrationView.as_view()),
    path(".well-known/oauth-authorization-server", AuthorizationServerMetadataView.as_view()),
    path(".well-known/oauth-protected-resource",   ProtectedResourceMetadataView.as_view()),
]

Both hooks are optional: omit GET_USER_ORGS for a consent page without an org picker; omit GET_OR_CREATE_PER_ORG_APP to leave tokens on the shared Application. The .well-known/ metadata URLs are built from the incoming request host, so one deployment serves correct values via localhost, a tunnel, staging, or production with no per-env config.

Customising the request

Attach extra state (tenant, feature flags, trace ids) before the ViewSet runs:

def attach_tenant(request, original_request):
    request.tenant = original_request.tenant

mcp = DRFMCP("myapp", prepare_request=attach_tenant)

Running the tests

uv sync
uv run pytest

License

MIT. See LICENSE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

django_rest_mcp-0.2.4.tar.gz (60.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

django_rest_mcp-0.2.4-py3-none-any.whl (22.1 kB view details)

Uploaded Python 3

File details

Details for the file django_rest_mcp-0.2.4.tar.gz.

File metadata

  • Download URL: django_rest_mcp-0.2.4.tar.gz
  • Upload date:
  • Size: 60.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for django_rest_mcp-0.2.4.tar.gz
Algorithm Hash digest
SHA256 714705a1d87a1bcc1916f91324af0f5376e44e6e4e6011a11055a3fc4b637c32
MD5 9ccb29e5a1a659ccc277049ec7884089
BLAKE2b-256 2fa29a5a54bdbd5fd1436ed62a41fe82dd2ab0a05d00bfa3e1e13864da97a0bc

See more details on using hashes here.

Provenance

The following attestation bundles were made for django_rest_mcp-0.2.4.tar.gz:

Publisher: ci.yml on pescheckit/django-rest-mcp

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file django_rest_mcp-0.2.4-py3-none-any.whl.

File metadata

  • Download URL: django_rest_mcp-0.2.4-py3-none-any.whl
  • Upload date:
  • Size: 22.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for django_rest_mcp-0.2.4-py3-none-any.whl
Algorithm Hash digest
SHA256 4f2b296c376e023c95b6f018f59fded92b092853eb1a9dedf506463a120dd4f9
MD5 3585e856948152fee1052f75bda6507d
BLAKE2b-256 35d13942c6ba8f6479d162ee66c2b6b1a8f1357f26976592aca28eafa6931106

See more details on using hashes here.

Provenance

The following attestation bundles were made for django_rest_mcp-0.2.4-py3-none-any.whl:

Publisher: ci.yml on pescheckit/django-rest-mcp

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page