Skip to main content
Glama
paritoshv

django-admin-mcp

by paritoshv

django-admin-mcp

Give an AI agent read-only access to your Django admin data over MCP — scoped to exactly what the signed-in staff user could already see in the admin. No new data surface, just a new protocol over the one you already trust.

agent ──stdio──▶ shim ──HTTPS(x-api-key)──▶ /admin-mcp/ views ──▶ ModelAdmin (perms + queryset) ──▶ read replica

Every read is driven through the model's own ModelAdmin, so the agent sees precisely what that user sees in /admin/ — same permission checks, same row scoping, same search — with secrets redacted and cost gates on every query.

Why it's different

  • Permission-faithful by construction. Access is gated by ModelAdmin.has_view_permission (model and object level), scoped by ModelAdmin.get_queryset, and searched via ModelAdmin.get_search_results — the exact calls the admin UI makes. A user with view rights on three models sees three models. There is no parallel permission system to keep in sync.

  • Zero-copy auth. No API keys to mint, paste, or rotate. The MCP shim runs a loopback browser sign-in (OAuth-style PKCE): it opens /auth/, catches a one-time code on a 127.0.0.1 listener, and exchanges it for a scoped, sliding-expiry token stored per-host at 0600. Expired token → the agent silently re-auths. The token authenticates to this endpoint only, so a leak can't reach any other API.

  • Safe to point at production. Reads route to a configurable replica alias, run under a per-query statement timeout, never issue an unbounded COUNT(*), refuse deep pagination, and are per-user rate-limited. Secret-looking fields (password, token, api_key, …, incl. keys nested in JSON columns) come back ***. Whole models can be hidden by policy.

  • Drop-in. One include() in your URLconf, a few optional settings, zero required dependencies beyond Django. Works with any MCP client (Claude Code, Codex, …) — the shim is stdlib-only.

Related MCP server: django-admin-mcp-api

Install

pip install django-admin-mcp
# settings.py
INSTALLED_APPS = [..., "admin_mcp"]
ADMIN_MCP_BASE_PATH = "/admin-mcp"          # must match where you mount the urls

# urls.py
from django.urls import include, path
urlpatterns = [
    path("admin/", admin.site.urls),
    path("admin-mcp/", include("admin_mcp.urls")),
]

That's the minimum. Then a staff user opens https://<your-host>/admin-mcp/auth/ and copies the one command it shows for their agent.

Try it in 60 seconds

git clone https://github.com/paritoshv/django-admin-mcp && cd django-admin-mcp/example
python manage.py migrate
python manage.py createsuperuser
python manage.py runserver
# open http://127.0.0.1:8000/admin-mcp/auth/  → follow the one-liner it prints

The demo app ships an Author/Book model (with a redacted api_key field) and a SecretRecord model that's hidden from MCP via ADMIN_MCP_HIDDEN_MODELS — so you can see scoping, redaction, and model-hiding immediately.

MCP tools

Tool

Maps to

Notes

admin_list_models

which models you may view

discovery entry point

admin_get_model_schema

fields, relations, search_fields, list_filter, sortable fields

read this to know what's queryable

admin_search

the changelist

query needs search_fields; filters must be within list_filter; bounded count

admin_get

the change view

one row by pk, object-level permission enforced

authenticate

the loopback sign-in

usually automatic; call to sign in ahead of time

Endpoints

Mounted under ADMIN_MCP_BASE_PATH. model is app_label.ModelName.

Path

Params

Auth

Returns

models/

staff

models the caller may view

schema/

model

staff

fields, relations, search/filter/sort surface

search/

model, query?, filters?, order_by?, page?, page_size?

staff

changelist rows + bounded count

object/

model, pk

staff

one object

auth/

redirect_uri?, state?, code_challenge?

browser session

setup page, or the loopback PKCE redirect

auth/token/

code, code_verifier (POST)

one-time PKCE code

exchanges a loopback code for a fresh token

shim/

staff

the stdio shim source (so setup is one curl)

Configuration

Everything is optional; defaults are safe.

Setting

Default

Purpose

ADMIN_MCP_ENABLED

True

master on/off

ADMIN_MCP_ENABLED_HOOK

dotted path to callable() -> bool — a runtime killswitch (feature flag / infra toggle) with no deploy

ADMIN_MCP_READONLY_DB

"default"

DB alias reads route to; point at a replica in prod

ADMIN_MCP_HIDDEN_MODELS

[]

"app.Model" labels never served (and hidden from discovery)

ADMIN_MCP_HIDDEN_MODELS_HOOK

dotted path to callable(model) -> bool for dynamic hiding (e.g. an encrypted/PII data-classification router)

ADMIN_MCP_CACHE_ALIAS

"default"

Django cache backing tokens/codes/throttle — use a shared Redis/Memcached in production

ADMIN_MCP_SECRET_MARKERS

password, secret, token, api_key, …

substrings that mark a field for redaction

ADMIN_MCP_SERVER_NAME

"admin-mcp"

MCP server name on the setup page; scope per environment

ADMIN_MCP_BASE_PATH

"/admin-mcp"

where the app is mounted (advertised to the shim)

ADMIN_MCP_PAGE_SIZE / _MAX_PAGE_SIZE / _COUNT_CAP / _STATEMENT_TIMEOUT_MS

25 / 100 / 1000 / 5000

pagination + cost caps

ADMIN_MCP_THROTTLE_AT / _THROTTLE_TIMEFRAME

120 / 60

per-user rate limit

ADMIN_MCP_TOKEN_TTL_SECONDS / _TOKEN_MAX_LIFETIME_SECONDS / _CODE_TTL_SECONDS

86400 / 604800 / 60

token idle window, absolute lifetime, one-time-code TTL

Production notes

  • Set ADMIN_MCP_CACHE_ALIAS to a shared cache (Redis/Memcached). The default LocMem cache is per-process, so tokens minted on one worker won't resolve on another.

  • Point ADMIN_MCP_READONLY_DB at a read replica so agent reads never touch the primary.

  • Wire ADMIN_MCP_ENABLED_HOOK to a feature flag if you want to kill the endpoint without a deploy.

Security model

  • Auth: a live admin session, or a scoped token in the x-api-key header. The token is a random opaque string; only its SHA-256 is stored. Idle 24h expiry that slides on use, plus a 7-day absolute cap. It resolves to an active staff user on every call, so deactivating the account or revoking staff invalidates it immediately, TTL notwithstanding.

  • Loopback PKCE refresh: /auth/ only ever redirects the one-time code to a validated http://127.0.0.1|localhost URI — never an external host — and the code (60s, single-use) is worthless without the PKCE verifier held by the shim that started the flow.

  • Redaction runs after honouring each admin's exclude/fields whitelist — defence in depth, so even a mis-configured admin can't leak a secret column into an LLM context.

  • Never evaluates list_display callables (they can trigger N+1s or side effects); serialises concrete fields only.

  • Audit trail: one structured record per call (who/what/outcome/timing) on the admin_mcp.audit logger — never the response values, so the log isn't a second data-egress path.

Tests

python runtests.py

License

MIT

Related MCP Connectors

Related MCP Servers