django-unfold-agentic-layer
Health Uyari
- License — License: MIT
- Description — Repository has a description
- Active repo — Last push 0 days ago
- Low visibility — Only 6 GitHub stars
Code Basarisiz
- rm -rf — Recursive force deletion command in orca-docker-vm/docker-base-image.sh
Permissions Gecti
- Permissions — No dangerous permissions requested
Bu listing icin henuz AI raporu yok.
Turn your Django Unfold admin into an MCP server: AI agents (Claude, Cursor, Copilot) get Unfold docs and your live Django admin — models, filters, actions — with admin permissions and MCP OAuth.
Your Django Unfold admin is already an MCP server
Add one Django app — and AI agents can work with all your models, actions and permissions. Out of the box.
English · Русский
[!WARNING]
Experiment. Do not use in production yet. This project is an experiment with a large share of vibe coding engineering. APIs and behavior can change.
But testing is very welcome — if something breaks, please open an issue!
Live demo
Try it on a demo Unfold admin: https://django-formula-admin-agentic.fly.dev/admin/ (login demo / demo).
claude mcp add --transport http unfold-mcp-demo "https://django-formula-admin-agentic.fly.dev/mcp/"
Then run /mcp in Claude Code, choose Authenticate and sign in with demo / demo. More about the demo: JargeZ/formula.
What it is
You already described your data in the admin: models, list_filter, search, forms, actions, permissions.
This app gives all of it to Claude, Cursor or any other MCP client. You do not write tools or a separate API.
Add it to INSTALLED_APPS, include its urls.py — and your project serves an MCP endpoint at /mcp.
No separate process, works under both WSGI and ASGI.
- Your admin, live — every registered model becomes MCP resources (detail + list with your
list_filter/search) andcreate/update/deletetools - All your actions — every admin and Unfold action becomes a tool, with its form as parameters
- Your permissions — an agent sees and does exactly what its user can do in the admin; validation goes through the model's admin form
- Standard MCP OAuth — clients log in through your admin login + consent page, only active staff users
- Unfold docs for agents — 25 tools with the official Django Unfold docs, so agents stop inventing settings and imports
Installation
Requirements: Python 3.11+, Django 5.2+, django-unfold 0.91+.
1. Install the package from git:
# uv
uv add "django-unfold-agentic-layer @ git+https://github.com/JargeZ/django-unfold-agentic-layer.git#subdirectory=packages/django-unfold-agentic-layer"
# poetry
poetry add "git+https://github.com/JargeZ/django-unfold-agentic-layer.git#subdirectory=packages/django-unfold-agentic-layer"
Pin a branch, tag or commit with .git@<ref>#subdirectory=….
2. Add it to settings.py:
INSTALLED_APPS = [
"unfold", # before django.contrib.admin
"django.contrib.admin",
"django.contrib.auth", # required: /mcp authenticates staff users
"django.contrib.contenttypes",
"django.contrib.sessions", # required: admin login in the OAuth flow
"django.contrib.messages",
"django.contrib.staticfiles",
"django_unfold_agentic_layer", # exactly this string
# your apps
]
SessionMiddleware and AuthenticationMiddleware must be in MIDDLEWARE (they are in any project with the admin).
3. Include the URLs in the project's root urls.py:
from django.contrib import admin
from django.urls import include, path
urlpatterns = [
path("admin/", admin.site.urls),
path("", include("django_unfold_agentic_layer.urls")), # → /mcp, /mcp/…
]
[!NOTE]
Every route of the app lives undermcp/, so it won't collide with yours (e.g. django-oauth-toolkit at/o/).
Need a prefix?path("agent/", include(...))gives/agent/mcp.
4. Apply migrations (OAuth clients and hashed tokens live in the app's own tables):
python manage.py migrate
Connecting clients
Clients log in on their own via standard MCP OAuth: the browser opens your admin login, then a consent page.
Claude Code
claude mcp add --transport http unfold https://your-project.example/mcp
# then in Claude Code: /mcp → unfold → Authenticate
Cursor — .cursor/mcp.json:
{
"mcpServers": {
"unfold": { "url": "https://your-project.example/mcp" }
}
}
MCP Inspector — for debugging:
npx @modelcontextprotocol/inspector
# Transport: Streamable HTTP, URL: http://localhost:8000/mcp
[!TIP]
Local development without OAuth — only honored whenDEBUG = True:UNFOLD_AGENTIC_LAYER_UNAUTHORIZED = TrueRequests without a token act as the first active superuser; permission filtering still applies.
Every such request logs a warning (
django_unfold_agentic_layer.mcp), and a second one when it comes from a non-loopback address.
The app also logs a warning once at startup (django_unfold_agentic_layer.config), because gunicorn and uvicorn do not run system checks.
HTTPS and reverse proxies
MCP OAuth requires https (plain http works only on localhost/127.0.0.1). All OAuth URLs are
built from the incoming request with Django's standard request.build_absolute_uri(), so behind a
TLS-terminating proxy (nginx, Traefik, a PaaS load balancer) tell Django the original scheme — the
same setting the admin and CSRF already rely on:
# settings.py — only if your proxy sets this header and strips it from client requests
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
USE_X_FORWARDED_HOST = True # only if the proxy rewrites Host
Without it, the OAuth endpoints answer 500 with "error": "server_error" and a description of
exactly this fix instead of advertising unusable http:// URLs.
What the agent gets
| Primitive | Example | What it does |
|---|---|---|
| Doc tools | unfold_filters, unfold_search_docs |
Django Unfold docs, including all third-party integrations |
| Detail resource | dj-admin://blog/blogpost/42/ |
One object's fields as JSON + Markdown; FK/M2M as links (up to 100 per M2M field, with a note when there are more) |
| List resource | dj-admin://blog/blogpost/{?params} |
Your list_filter/search params, limit/offset/order_by |
| Tools | create_blog_blogpost, update_blog_blogpost |
Through the admin form; errors come back per field |
| Tool | delete_blog_blogpost |
Asks for confirmation before deleting |
| Action tools | run_blog_blogpost_publish_posts |
Every admin/Unfold action (bulk, list, row, detail) with its form; DANGER ones ask for confirmation |
By default users and groups are not exposed (MODELS setting) — opt them in deliberately.
Everything goes through the admin's own has_*_permission checks, re-run on every request — the agent never sees
or does what its user can't do in the admin right now; granting or revoking a permission applies to the very next call.
Writes run like the admin's own views: the same save_model/save_related/delete_model hooks in the same
transaction, the same history entries (an object's "History" shows what the agent changed, under its user), and a delete
is refused when it would cascade onto objects the user can't delete.
[!IMPORTANT]
The agent gets exactly the admin's permissions — including the admin's blind spots. Before you connect agents, check
what a user can do in the browser: actions withoutpermissions=run for view-only users;UserAdminlets anyone withchange_usersetis_superuser; object permissions (e.g. django-guardian) apply only if yourget_querysetand
actions enforce them. Runmanage.py check --deploy— it warns when confirmations are not shared between workers.
Access and sessions
- On the consent page the user picks the login's scopes, on top of their own permissions:
mcp:read(resources, always granted),mcp:write(create/update tools and actions) andmcp:destructive(delete tools andDANGERactions). Tools outside the scopes are not even listed, and every call re-checks its scope. - Only active staff users holding the
django_unfold_agentic_layer.use_mcppermission ("Can use the admin through MCP" — grant it per user or group; superusers always have it) can log in;is_active/is_staffand the permission are re-checked on every request — un-staffing someone or taking the permission away cuts off their tokens immediately.REQUIRED_PERMISSION = Falselets every active staff user in. - Changing a user's password revokes their MCP tokens, the same way Django logs out their browser sessions (the session auth hash;
SECRET_KEY_FALLBACKSkeeps them valid until the old key leaves it). - The login (consent) page goes through
admin.site.admin_view, so your admin site's ownhas_permissionapplies — a custom site's 2FA or IP rules guard the MCP login exactly like the admin. - The consent page names where the code goes and warns about what it can't verify: a return address off your computer, an app's own scheme (
cursor://), a self-declared client name — and suggests a separate, less privileged user to superusers. - The Django session cookie does not authenticate
/mcp— only theBearertoken does. - A login lasts
SESSION_TTL(default 1 day), no refresh tokens. - An authorization code works once; presenting it again also revokes the token it was exchanged for (RFC 6749 §4.1.2).
- Clients and tokens are visible in the admin — deleting one revokes it. A deleted client gets a page asking the person to
clear the saved authentication in their MCP client (Claude Code:/mcp→ server → Clear authentication) and reconnect.
Settings
All optional — an UNFOLD_AGENTIC_LAYER dict, the same override pattern as Unfold's UNFOLD:
from datetime import timedelta
UNFOLD_AGENTIC_LAYER = {
"SESSION_TTL": timedelta(hours=8), # MCP login lifetime, default 1 day
# CACHES alias that remembers answered confirmations of dangerous tools, so one
# confirmation can't be replayed into many runs. Default "default" — with several
# worker processes it must be a shared backend (Redis, DB, Memcached), not LocMem.
"CONFIRMATION_CACHE": "default",
# Only users with the use_mcp permission (superusers always) may use MCP. Default True.
"REQUIRED_PERMISSION": True,
# Which admin models MCP exposes: "app_label" or "app_label.model" entries.
# INCLUDE None = all; EXCLUDE None = your user model and auth.group (so an agent can't
# grant itself permissions); [] = exclude nothing.
"MODELS": {"INCLUDE": None, "EXCLUDE": None},
# True: create/update tools and every admin action ask the user to confirm first,
# like delete tools and DANGER actions always do. Default False.
"CONFIRM_WRITES": False,
}
The full list is in conf.py.
Logging
The library logs under django_unfold_agentic_layer, split by area. Configure them with Django's standardLOGGING setting — all at once by the parent name, or one area by its full name:
| Logger | What it reports |
|---|---|
django_unfold_agentic_layer.mcp |
Rejected /mcp calls (method + status at WARNING, response body at DEBUG), dev-mode warnings |
django_unfold_agentic_layer.oauth |
The OAuth flow: discovery, login, consent, tokens |
django_unfold_agentic_layer.tools |
Errors inside tool and resource handlers |
django_unfold_agentic_layer.builder |
Building the per-user server from the admin registry |
django_unfold_agentic_layer.config |
Unsafe settings, once at startup (the app's system-check warnings) |
LOGGING = {
"version": 1,
"handlers": {"console": {"class": "logging.StreamHandler"}},
"loggers": {
"django_unfold_agentic_layer": {"handlers": ["console"], "level": "INFO"},
# Response bodies can hold admin data: enable DEBUG only while debugging.
"django_unfold_agentic_layer.mcp": {"level": "DEBUG"},
},
}
Production checklist
- Serve over https (behind a proxy:
SECURE_PROXY_SSL_HEADER) and keepALLOWED_HOSTSstrict — OAuth URLs are built from theHostheader. - Grant
use_mcponly to the people who should connect agents (REQUIRED_PERMISSION, on by default), ideally to dedicated users with just the permissions the agent needs — not superusers. - Keep users and groups out of MCP (
MODELS, the default) unless you mean it; narrowMODELS["INCLUDE"]to what agents need. - Consider
CLOSED_CLIENT_REGISTRATION = TrueandCONFIRM_WRITES = Truefor sensitive data. - Point
CONFIRMATION_CACHEat a cache all workers share and runmanage.py check --deploy. - Never set
UNFOLD_AGENTIC_LAYER_UNAUTHORIZEDoutside local development. - Rate-limit
/mcp/registerand/mcp/tokenat your proxy; collectdjango_unfold_agentic_layer.*logs.
Roadmap
- Admin actions (
@action) as MCP tools - Submit-line actions (
actions_submit_line) - Stateful mode / SSE for server-initiated notifications
-
allowed_hosts/allowed_originsconfiguration via settings - Custom field rendering
Details in CLAUDE.md and docs/specs.
Development
The repo is a uv workspace; the package lives in packages/django-unfold-agentic-layer/.
uv sync
uv run pre-commit install # secret scanning (gitleaks, trufflehog) + ruff, and a Conventional Commits check on messages
cd packages/django-unfold-agentic-layer
uv run pytest
cd ../..
uv run tox run-parallel -c packages/django-unfold-agentic-layer # the CI matrix: Python 3.11–3.14 × Django 5.2, 6.0, 6.1
The tests boot a throwaway Django project (django_test_app/) and drive real MCP requests through /mcp.
Architecture and conventions are in CLAUDE.md.
[!TIP]
Developing with AI agents? Give each agent its own isolated, disposable workspace — check out
orca-recipes: per-workspace Docker environments for
next-gen dev setups (Claude Code, Cursor, OpenCode). This repo ships one:orca.yaml+dev.Dockerfile.
Contributing
Issues and pull requests are welcome!
Credits
Inspired by rissets/mcp-django-unfold — the project that sparked the idea of building a full MCP compatibility layer for Django Unfold. The Unfold documentation skills are taken from that repository; everything else is a completely new implementation.
License
Yorumlar (0)
Yorum birakmak icin giris yap.
Yorum birakSonuc bulunamadi