django-admin-fastmcp
Provides tools to interact with a Django admin site, including listing models, searching and retrieving objects, creating, updating, and deleting objects, running admin actions, and viewing object history, with permissions delegated to each model's ModelAdmin.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@django-admin-fastmcpshow me all orders that are pending"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
django-admin-fastmcp
A reusable Django app that exposes the Django admin as an MCP server, built on FastMCP.
Every tool call runs as the staff user who owns the bearer token. Every tool call asks the
ModelAdmin for permission first. A superuser can do everything a superuser can do in the
admin. A staff user can do exactly what that staff user can do in the admin, and nothing
more.
SPEC.md is the full specification.
How it works
Three rules define the package:
No parallel permission system. Authorization delegates to the
ModelAdminmethods:has_view_permission,has_add_permission,has_change_permission,has_delete_permission,get_queryset,get_readonly_fields, andget_actions. Aget_querysetoverride that hides rows hides them from MCP too.No parallel data surface. Writes go through the admin's own
ModelFormandsave_model, then record aLogEntry. The admin history page stays truthful.Fail closed. Every unresolved lookup, missing
ModelAdmin, unknown action, unknown field, and unknown tool denies the call.
Installation
uv add django-admin-fastmcpAdd the app to your settings:
INSTALLED_APPS = [
...,
"django.contrib.admin",
"django_admin_fastmcp",
]
ADMIN_FASTMCP = {
"SERVER_NAME": "acme-admin",
"EXCLUDE_MODELS": ("auth.Permission", "auth.Group"),
"WRITABLE_MODELS": (), # empty means no writes at all
}Mount the OAuth endpoints on the same site as the admin:
# urls.py
urlpatterns = [
# RFC 8414 fixes this one at the site root.
path("", include("django_admin_fastmcp.well_known_urls")),
# This prefix is yours to choose. Match it to the path in MCP_URL.
path("admin/mcp/", include("django_admin_fastmcp.urls")),
path("admin/", admin.site.urls),
]The package hardcodes no prefix. Every URL the metadata document advertises comes
from reverse(), so a project that mounts the endpoints at /backoffice/oauth/
gets that in discovery and clients follow it. Two rules: the well-known document
belongs at the root, because a client derives its URL from the issuer, and the
endpoints should sit on the same site as the admin, because the consent page
rides the admin session cookie.
Apply the migrations:
python manage.py migrate django_admin_fastmcpNothing else. No per-model registration, no mixin, no decorators. The server exposes whatever the admin already exposes.
Connect a client
Run the server (see Deployment), then register it:
# Claude Code
claude mcp add --transport http acme-admin https://<host>/admin/mcpUse the path with no trailing slash. /admin/mcp/ answers a 307 redirect to
/admin/mcp, and not every client follows a redirect on POST.
No token, no header. The first call starts the standard MCP OAuth flow:
The client opens your browser at the authorize page on the Django site.
Your admin session cookie identifies you. If you are logged out, the normal admin login appears first.
A consent page shows the client name and what approval means. You approve.
The client receives its tokens and connects. It refreshes them by itself.
Any MCP client that speaks streamable HTTP with OAuth works the same way, for example
Cursor or a FastMCP Client.
Rules around access:
Any staff user can authorize a client, for themselves only.
A grant acts with your own admin permissions, never more. There is no separate permission system: whoever may change a model in the admin may change it over MCP, when the server lists that model in
WRITABLE_MODELS.Refresh tokens expire after
REFRESH_TOKEN_TTL_DAYS(default 90), so re-consent happens that often. Revocation is an admin action on the grant changelist.
Tools
Eleven generic tools, mounted under the namespace admin, so wire names are
admin_list_models and so on. Each takes model as "app_label.ModelName". The tool
list is static. What varies per user is what each tool lets that user see and do.
Read
Tool | Arguments | Returns |
| none | Every exposed model this caller may view, with permission flags. |
|
| Fields, list display, filters, search fields, readonly fields, and available actions. |
|
| Rows plus |
|
| One serialized instance. |
|
| Admin log entries for that object, newest first. |
|
| Admin log entries, scoped to the caller unless the caller is a superuser. |
Write
Write tools need the model listed in WRITABLE_MODELS; your own admin permissions
decide the rest, per model and per object. A model outside the list refuses every write
and every action, whoever calls. Leave sensitive models out, an event log for example,
and no MCP client can ever write to them.
Tool | Arguments | Behavior |
|
| Validates through the admin form, then saves and logs. |
|
| Partial update. Readonly fields are ignored. |
|
| Without |
|
| Runs an admin action. Without |
|
| Resolves a foreign-key value to a primary key by searching the related model. |
Every returned row carries pk as a string and an admin_url, so an agent can hand a
person a link into the real admin.
Settings
All keys live in the ADMIN_FASTMCP dict. An unknown key is an error at startup.
Key | Default | Meaning |
|
| Name the MCP server advertises. |
|
| Dotted path to the |
|
| Allowlist of |
|
| Denylist. Supports |
|
| Models that accept writes. Empty means no writes, whoever calls. |
|
| Tool names removed from the catalogue entirely. |
|
| Substring match on field names. Values read |
|
| Cap on |
|
| Cap on |
|
| Access token lifetime. Clients renew with the refresh token. |
|
| Refresh token lifetime. Re-consent happens this often. |
|
| Public URL of the Django site. It is the OAuth issuer, and the MCP server names it as its authorization server. |
|
| Public URL of the MCP endpoint. |
Set SITE_URL and MCP_URL for any real deployment. MCP_URL is the single
source of three things that must agree: the path the endpoint is served on, the
resource that discovery advertises, and the audience every token is bound to.
Its path defaults to /admin/mcp. A startup check refuses a MCP_URL with no
path, because then the whole origin would be advertised as the protected
resource.
Per-ModelAdmin knobs
Set these on a ModelAdmin class, no mixin needed:
class InvoiceAdmin(admin.ModelAdmin):
mcp_expose = False # hide this model from MCP entirely
mcp_fields = ("number", "total") # allowlist of serialized fields
mcp_exclude_fields = ("internal_note",) # denylist of serialized fieldsREDACT_FIELDS wins over mcp_fields. Listing a password field explicitly does not
reveal it.
Safety
An admin MCP server for a superuser is a remote shell over the production database, driven by a language model. The rails:
WRITABLE_MODELSdefaults to empty, so no model accepts writes until the deployment names it. Everything else is your ordinary Django permissions, asked through theModelAdminon every call.Access tokens are short-lived. Only salted hashes are stored, so a leaked database row cannot be replayed.
delete_objectandrun_actionpreview by default and change nothing untilconfirm=True.Every mutation records a
LogEntryattributed to the grant's user, with the client name in the change message, for example"Changed status. Via MCP (client: Claude Code).". A write that cannot record aLogEntryrolls back.The package's own models,
sessions.Session, andauthtoken.Tokenare never exposed, whatever the settings say.Keep
auth.Permissionandauth.Groupout ofWRITABLE_MODELS. An agent that can grant permissions can escape the permission model.
Deployment
Separate process. Run the MCP server next to your Django project:
python manage.py admin_mcp_serveIt serves the path from MCP_URL, which is /admin/mcp by default, on the port from
MCP_URL, or 8765 when that URL names no port. Both are overridable with --host and
--port. Nothing about your existing serving configuration changes. Route
/admin/mcp through your ingress to that port, and make sure the Authorization
header passes through.
Mounted (M3). Mount the server at /admin/mcp inside your project's asgi.py. One
constraint: dispatch on the exact path. The OAuth endpoints live directly below the same
prefix (/admin/mcp/authorize and friends) and Django must keep serving those, so a
dispatcher that sends everything under /admin/mcp to FastMCP would swallow them. The
recipe ships with milestone M3.
The server is stateless, so any instance behind a load balancer can serve any request.
Development
make install # bootstrap uv, pin Python, install dependencies
make test # run the permission matrix
make check # format, lint, typecheck, and test
make migrate # migrate the test project
make serve # run the MCP server against the test project on :8765/admin/mcp
make help # everything elseLicense
MIT
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Connectors
MCP server for AI dialogue using various LLM models via AceDataCloud
An MCP server that let you interact with Cycloid.io Internal Development Portal and Platform
Hosted MCP server connecting claude.ai, ChatGPT and other AI apps to your own computer
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/edelvalle/django-admin-fastmcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server