Skip to main content

Turns Django/Wagtail code into MCP tools (and resources) for Claude clients

Project description

django-mcp-kit

Turns Django/Wagtail code into MCP tools (and resources) for Claude clients — without coupling your business logic to any MCP framework.

  • Transport-neutral core. A registry + dispatcher own initialize / tools.list / tools.call; the official MCP SDK is used only at the wire (transports/sdk.py) and is swappable. No FastMCP.
  • Native Django authz. DRF-style permission_classes checked before run(); object permissions (Wagtail permissions_for_user) stay in your service layer.
  • Pluggable auth. Static bearer tokens and OAuth 2.1 resource server (django-oauth-toolkit) behind one Authenticator interface, with RFC 9728 discovery metadata and the 401 + WWW-Authenticate handshake.
  • On-ramps. Register a DRF ViewSet, expose a model (read-only by default), or declare MCP resources.

Install

pip install django-mcp-kit              # core: MCP SDK wire transport, OAuth, DRF on-ramp, singleserver
pip install "django-mcp-kit[wagtail]"   # + the optional Wagtail admin settings page

Batteries included. The MCP SDK + uvicorn (the only wire transport today), django-oauth-toolkit (OAuth resource server), DRF (the ViewSet/model on-ramp), and singleserver are all core dependencies — their imports stay lazy/contained, so the architectural boundary holds in code even though the packages ship by default. The one optional extra is [wagtail], for the Wagtail admin settings page (Django-only projects don't need Wagtail).

Quickstart

# settings.py
INSTALLED_APPS += ["django_mcp_kit"]
DJANGO_MCP_KIT = {
    "SERVER_NAME": "my-content",
    "AUTH_BACKENDS": [
        "django_mcp_kit.auth.OAuthResourceServer",
        "django_mcp_kit.auth.StaticBearer",
    ],
    "STATIC_BEARER_RESOLVER": "myapp.models:UserProfile.user_for_token",
    "OAUTH_ISSUER_URL": "https://example.com",
    "RESOURCE_SERVER_URL": "https://example.com/mcp",
    "REQUIRED_SCOPES": ["mcp"],
}

# urls.py — discovery + health for the Django site
path("", include("django_mcp_kit.urls")),  # /healthz, /.well-known/oauth-protected-resource
# myapp/mcp_tools.py — autodiscovered on app load
from django_mcp_kit import Tool, Schema, tool
from . import services

class PatchBlock(Tool):
    name = "patch_block"
    description = "Replace one homepage block by id; saves a DRAFT."

    class Input(Schema):
        block_id: str
        value: dict

    def run(self, user, block_id, value):           # sync — runs off the event loop
        return services.save_homepage_draft(user, block_id=block_id, value=value)

@tool(name="get_draft", description="Latest homepage draft.")
def get_draft(user) -> dict:
    return services.homepage_draft()
# Register a DRF ViewSet (one tool per action) or a model (read-only by default)
from django_mcp_kit.drf import register_drf_viewset
from django_mcp_kit.models import ModelToolset

register_drf_viewset(OrderViewSet, prefix="order")  # order_list, order_create, …

class ProductToolset(ModelToolset):
    model = Product
    actions = ["list", "retrieve"]   # add "create"/"update"/"delete" to opt in
    as_resource = True

Run it

python manage.py runserver_mcp --port 8810      # ASGI/uvicorn MCP process

Deployment

The MCP endpoint is always ASGI (Streamable HTTP uses SSE), but how it runs is a choice. All topologies run the same code — runserver_mcp serving the app from django_mcp_kit.asgi:get_application — only the process management differs. Pick by how your site is served and how much isolation you want:

Topology Site stays WSGI Process Best for
A — Co-located no (site → ASGI) one ASGI process single-service / already-ASGI sites
B — singleserver aux yes gunicorn boots the MCP process, workers share it dev == prod, no systemd
C — separate systemd unit yes independent ASGI daemon production SSE (recommended)

Health checks point at /healthz (a plain 200) — never /mcp, which is the Streamable-HTTP endpoint and returns 4xx to a bare GET.

A — Co-located (one ASGI process)

Mount the MCP app beside your Django ASGI app; requests to /mcp (and /healthz, /.well-known/...) go to MCP, everything else to Django:

# asgi.py
from django.core.asgi import get_asgi_application
from django_mcp_kit.asgi import mount

application = mount(get_asgi_application())   # serves /mcp on the same process

B — singleserver-managed aux (deploy/gunicorn.conf.py)

Your site stays WSGI/gunicorn. The first gunicorn worker boots the MCP process; all workers share it via an atomic socket lock (no systemd). Wire it in post_fork:

# gunicorn.conf.py
def post_fork(server, worker):
    from django_mcp_kit.services import connect
    connect()

Point your front-end proxy /mcp at DJANGO_MCP_KIT["PORT"] (default 8810). The shipped SingleServer uses health_check_url="/healthz" and a bounded graceful shutdown.

C — Separate systemd unit (deploy/django-mcp.service)

Run the MCP server as its own daemon — decoupled lifecycle, real graceful restart. The sample unit runs runserver_mcp with a bounded --timeout-graceful-shutdown (so long-lived SSE streams don't stall stop/restart) and Restart=always. Edit the paths/user, then:

sudo cp deploy/django-mcp.service /etc/systemd/system/
sudo systemctl daemon-reload && sudo systemctl enable --now django-mcp

nginx (deploy/nginx-mcp.conf)

For B and C, proxy /mcp to the MCP process. SSE requires buffering off and long read timeouts — the sample sets proxy_buffering off and proxy_read_timeout 3600s, and also proxies the /.well-known/oauth-protected-resource metadata. Add it inside your server {} block and reload nginx.

Authorization Server (OAuth) setup

This library is the Resource Server — it validates bearer tokens and serves the RFC 9728 discovery metadata. It does not provide the Authorization Server (the login, /o/authorize, /o/token, and the consent page). That role is django-oauth-toolkit (DOT), which ships as a dependency but must be wired up by the project:

# settings.py
INSTALLED_APPS += ["oauth2_provider"]
OAUTH2_PROVIDER = {
    "SCOPES": {"mcp": "Access MCP tools"},
    "PKCE_REQUIRED": True,   # public clients (browser/native, e.g. claude.ai)
}

# urls.py — mount the Authorization Server endpoints
path("o/", include("oauth2_provider.urls", namespace="oauth2_provider")),

Provisioning the OAuth client

Register an OAuth client (a DOT Application) per connector with the bundled command — idempotent, public + PKCE by default:

python manage.py create_mcp_oauth_client https://claude.ai/api/mcp/auth_callback \
    --name "My connector"        # name shown on the consent page; --skip-consent to auto-approve

The client name defaults to DJANGO_MCP_KIT["OAUTH_APP_NAME"] ("MCP connector") when --name is omitted. The command prints the Client ID to paste into the connector. django_mcp_kit.oauth_client.ensure_oauth_application(...) is the same helper if you'd rather provision from code.

The consent page

The consent screen is rendered by DOT's AuthorizationView at /o/authorize/ using its default template oauth2_provider/authorize.html (a plain approve/deny form). Whether it appears is controlled per-client by Application.skip_authorization:

  • skip_authorization=False (DOT default) — the user is shown the consent page on first authorization.
  • skip_authorization=True — consent is auto-approved (no page). Reasonable for a trusted first-party connector.

The name shown on that consent page is the Application.name — set it per client with create_mcp_oauth_client --name, or change the default via DJANGO_MCP_KIT["OAUTH_APP_NAME"]. To customise the consent UI, override oauth2_provider/authorize.html in your own templates directory. This library has no opinion on and no default for the consent page — it only consumes the access token DOT issues.

Configuring it from the Wagtail admin (optional)

For Wagtail projects, add the optional app to get a "MCP connector" page under the admin Settings menu that provisions/updates the client on save:

pip install "django-mcp-kit[wagtail]"    # Wagtail 6.x or 7.x
INSTALLED_APPS += ["django_mcp_kit.wagtail_connector"]
# then: python manage.py migrate

Fields: enable, the consent-page name, redirect URIs, and skip-consent. Access is gated by the change_mcpconnectorsettings permission — i.e. superusers only by default; delegate to specific staff by granting that permission via a Group. Wagtail is not a core dependency — it's the optional [wagtail] extra, so Django-only projects skip it.

Develop

poetry install
poetry run pytest

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_mcp_kit-0.1.0.tar.gz (29.3 kB view details)

Uploaded Source

Built Distribution

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

django_mcp_kit-0.1.0-py3-none-any.whl (38.1 kB view details)

Uploaded Python 3

File details

Details for the file django_mcp_kit-0.1.0.tar.gz.

File metadata

  • Download URL: django_mcp_kit-0.1.0.tar.gz
  • Upload date:
  • Size: 29.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/2.1.4 CPython/3.13.7 Darwin/25.3.0

File hashes

Hashes for django_mcp_kit-0.1.0.tar.gz
Algorithm Hash digest
SHA256 ba851279edd80930fff1c9502558cf02b2a134753b4c467369c78c1afadea59f
MD5 a1724e98e4bf95709093f32dde3dfa65
BLAKE2b-256 7238f1bf78409e4bbb5528db478286d36263412deea4fd89009d5fc04af2d36b

See more details on using hashes here.

File details

Details for the file django_mcp_kit-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: django_mcp_kit-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 38.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/2.1.4 CPython/3.13.7 Darwin/25.3.0

File hashes

Hashes for django_mcp_kit-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 21a8e6475875e2eebf8bf480c81bcc71c49d0242287fbe6d2f795e8f50b0b2e2
MD5 dd97aec74900055a3b6e01e6ee256833
BLAKE2b-256 7018d079191015ed947c22521702849998ae28b3227e45defe95d8bb828dbba1

See more details on using hashes here.

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