Exposing Models¶
This guide explains how to expose Django models via the MCP protocol using MCPAdminMixin.
Basic Usage¶
Add MCPAdminMixin to any ModelAdmin class:
from django.contrib import admin
from django_admin_mcp import MCPAdminMixin
from .models import Article
@admin.register(Article)
class ArticleAdmin(MCPAdminMixin, admin.ModelAdmin):
mcp_expose = True
Two-Level Exposure System¶
Django Admin MCP uses a two-level exposure system:
Level 1 — Discoverable Models¶
Models with MCPAdminMixin (but without mcp_expose = True) are discoverable:
class AuthorAdmin(MCPAdminMixin, admin.ModelAdmin):
pass # Discoverable via find_models, no direct tools
These models:
- Appear in
find_modelsresults withtools_exposed: false(filtered by module andviewpermission) - Show their field structure
- Are not advertised in
tools/list
mcp_expose controls both advertisement and reachability
Without mcp_expose = True, tools are neither advertised in tools/list nor callable: a client that explicitly issues tools/call for list_author gets the same "Model not found" error as for an unregistered model. Django permissions additionally gate every call on exposed models.
Level 2 — Fully Exposed Models¶
Models with mcp_expose = True are fully exposed:
These models expose 12 tools:
| Tool | Permission | Description |
|---|---|---|
list_<model> |
view | List instances with pagination/filtering |
get_<model> |
view | Get single instance by ID |
create_<model> |
add | Create new instance |
update_<model> |
change | Update existing instance |
delete_<model> |
delete | Delete instance |
describe_<model> |
view | Get field definitions |
actions_<model> |
view | List available admin actions |
action_<model> |
change | Execute admin action |
bulk_<model> |
varies | Bulk create/update/delete |
related_<model> |
view | Get related objects |
history_<model> |
view | View change history |
autocomplete_<model> |
view | Search suggestions |
Note
tools/list itself is not permission-filtered — tools for models the caller cannot access are still advertised and fail with a permission error at call time.
Mixin Placement¶
Mixin Order Matters
MCPAdminMixin should come before admin.ModelAdmin in the inheritance chain:
# Correct
class ArticleAdmin(MCPAdminMixin, admin.ModelAdmin):
pass
# Also correct with other mixins
class ArticleAdmin(MCPAdminMixin, SomeOtherMixin, admin.ModelAdmin):
pass
Configuring Exposed Behavior¶
List Display¶
list_display is reported by describe_<model> as admin metadata. It does not change which fields list_<model> returns — use mcp_fields/mcp_exclude_fields for that:
class ArticleAdmin(MCPAdminMixin, admin.ModelAdmin):
mcp_expose = True
list_display = ['title', 'author', 'published', 'created_at']
Search Configuration¶
search_fields controls which fields autocomplete_* searches. Without it, all CharField/TextField fields are searched:
class ArticleAdmin(MCPAdminMixin, admin.ModelAdmin):
mcp_expose = True
search_fields = ['title', 'content']
Queryset Scoping¶
By default (mcp_use_admin_queryset = True), every tool that looks up rows (list_*, get_*, update_*, delete_*, bulk_*, admin actions, related_*, history_*, autocomplete_*) starts from ModelAdmin.get_queryset(request) — proxy filters, soft-delete, and multi-tenant scoping match the admin changelist. Set it to False to use model.objects.all() instead:
class ArticleAdmin(MCPAdminMixin, admin.ModelAdmin):
mcp_expose = True
mcp_use_admin_queryset = False # bypass get_queryset() scoping
Ordering¶
ordering sets the default order for list results:
class ArticleAdmin(MCPAdminMixin, admin.ModelAdmin):
mcp_expose = True
ordering = ['-created_at'] # Newest first
Readonly Fields¶
Attempts to update readonly_fields return an error:
class ArticleAdmin(MCPAdminMixin, admin.ModelAdmin):
mcp_expose = True
readonly_fields = ['created_at', 'updated_at', 'view_count']
Field Filtering¶
Control which fields are exposed in MCP responses using field filtering. This is critical for preventing sensitive data exposure.
MCP-Specific Field Control¶
Use mcp_fields and mcp_exclude_fields for MCP-specific field visibility:
class UserAdmin(MCPAdminMixin, admin.ModelAdmin):
mcp_expose = True
# Only expose these fields via MCP
mcp_fields = ['username', 'email', 'first_name', 'last_name', 'is_active']
# Never expose password, even though it exists in the model
Alternatively, exclude specific fields:
class APIKeyAdmin(MCPAdminMixin, admin.ModelAdmin):
mcp_expose = True
# Exclude sensitive fields from MCP responses
mcp_exclude_fields = ['secret_key', 'api_token', 'private_data']
Django Admin Field Fallback¶
If mcp_fields or mcp_exclude_fields are not set, django-admin-mcp falls back to Django admin's fields and exclude attributes:
class ArticleAdmin(MCPAdminMixin, admin.ModelAdmin):
mcp_expose = True
fields = ['title', 'content', 'author', 'published'] # Used if mcp_fields not set
Field Filtering Rules¶
- MCP-specific takes precedence — If
mcp_fieldsis set, it overridesfields - MCP-specific exclusion takes precedence — If
mcp_exclude_fieldsis set, it overridesexclude - Exclusion wins over inclusion — If a field is in both
mcp_fieldsandmcp_exclude_fields, it's excluded - No configuration = all fields — If no field configuration is provided, all model fields are exposed
Non-editable fields are always excluded
Serialization uses Django's model_to_dict(), which skips every editable=False field — including auto primary keys, auto_now_add/auto_now timestamps, and any field declared editable=False. This means list_*/get_* payloads do not contain an id key; use get_*'s input id (or create_*'s returned id) to track object identity. Regular date fields like expires_at are included normally.
Example — Protecting Sensitive Data¶
from django_admin_mcp import MCPAdminMixin
@admin.register(Customer)
class CustomerAdmin(MCPAdminMixin, admin.ModelAdmin):
mcp_expose = True
# Never expose payment credentials via MCP
mcp_exclude_fields = ['card_number', 'internal_notes']
list_display = ['name', 'email', 'is_active']
When listing or getting customers via MCP, excluded fields are filtered out:
Foreign keys serialize as bare primary keys and many-to-many fields as lists of primary keys. card_number and internal_notes are not included; neither are non-editable fields such as the auto primary key or auto_now_add timestamps.
Custom Actions¶
Admin actions are automatically exposed:
@admin.action(description='Mark as published')
def publish(modeladmin, request, queryset):
queryset.update(published=True)
@admin.action(description='Mark as draft')
def unpublish(modeladmin, request, queryset):
queryset.update(published=False)
class ArticleAdmin(MCPAdminMixin, admin.ModelAdmin):
mcp_expose = True
actions = [publish, unpublish]
Inline Models¶
Inline models are included in get_* responses when include_inlines is set to true:
class CommentInline(admin.TabularInline):
model = Comment
extra = 0
class ArticleAdmin(MCPAdminMixin, admin.ModelAdmin):
mcp_expose = True
inlines = [CommentInline]
When fetching an article with include_inlines: true, its comments are included under the _inlines key, grouped by the inline model's lowercase name:
{
"title": "My Article",
"_inlines": {
"comment": [
{"text": "Great article!"},
{"text": "Thanks for sharing"}
]
}
}
Inlines can also be created, updated, and deleted through update_<model>'s inlines parameter — see CRUD Operations.
Best Practices¶
Start Conservative¶
Begin with discoverable models, then expose as needed:
# Start with discovery only
class SensitiveDataAdmin(MCPAdminMixin, admin.ModelAdmin):
pass # Can see structure, but no direct access
# Later, if needed:
class SensitiveDataAdmin(MCPAdminMixin, admin.ModelAdmin):
mcp_expose = True
Use Meaningful Search Fields¶
Configure search fields for useful autocomplete:
class UserAdmin(MCPAdminMixin, admin.ModelAdmin):
mcp_expose = True
search_fields = ['username', 'email', 'first_name', 'last_name']
Protect Sensitive Fields¶
Use mcp_exclude_fields to prevent sensitive data exposure:
class UserAdmin(MCPAdminMixin, admin.ModelAdmin):
mcp_expose = True
# Never expose password or sensitive authentication data
mcp_exclude_fields = ['password', 'security_token', 'api_secret']
readonly_fields = ['password', 'last_login', 'date_joined']
For models with many fields, use mcp_fields to explicitly allowlist safe fields:
class CustomerAdmin(MCPAdminMixin, admin.ModelAdmin):
mcp_expose = True
# Only expose non-sensitive customer data
mcp_fields = ['name', 'email', 'company']
# This excludes: credit_card, ssn, internal_notes, etc.
Next Steps¶
- Token Management — Configure access tokens
- Permissions — Understand the permission system
- Tools Reference — Explore all available tools