djcrud_mcp design¶
This document is the implementation spec for djcrud_mcp. Read it before writing code or the Agents (MCP bridge) walkthrough.
Problem¶
Agents need machine CRUD without a hand-written SDK. djcrud already exposes
ModelViewSet on /api/<model>/ with the same
djcrud.permissions registry as HTML. djcrud_mcp turns those
registered ViewSets into stdio MCP tools — no per-action decorators.
Goals¶
Register once —
djcrud_drf.site.register(ItemViewSet)for HTML/DRF, plusdjcrud_mcp.site.register(ItemMcp)listing that ViewSet (orkey = "default"with no filter) for MCP.Permissions on the server —
ModelViewSetalready callshas_permission()andget_queryset(); the MCP bridge is a Bearer HTTP proxy.Automatic tool names —
{model}_{action}(e.g.item_list,item_create) derived from the ViewSet model and DRF action, matchingACTION_SHORTCODESsemantics.No ``@extend_schema`` on CRUD — drf-spectacular already documents registered ViewSets; MCP reads
GET /api/schema/and filters by known API paths.Host-owned profiles —
McpProfileclasses register ondjcrud_mcp.site; remote clients fetchGET /api/mcp/profiles/{key}/.
Non-goals¶
User MCP server vaults (application code, e.g. Tildette
djacp_mcp).Django URL routes for MCP stdio transport.
Re-declaring permissions in the MCP client.
Client-side tool definitions outside OpenAPI schema.
Architecture¶
┌──────────────────┐ stdio MCP ┌─────────────────────┐
│ Agent │ ◄────────────────► │ djcrud_client │
└──────────────────┘ └──────────┬──────────┘
│
GET /api/mcp/profiles/{key}/ + GET /api/schema/
Bearer HTTP /api/<model>/
▼
┌─────────────────────┐
│ djcrud_drf │
│ ModelViewSet │
└──────────┬──────────┘
▼
┌─────────────────────┐
│ djcrud.permissions │
└─────────────────────┘
CRUD discovery (default)¶
Registered ViewSets — on the Django host, introspect
djcrud_drf.siteto learn each model’s API path:/api/{model.__name__.lower()}/.Schema fetch —
GET /api/schema/; keep operations whose path matches a profile’s ViewSet prefixes.Tool naming — for each standard DRF action on that path:
HTTP
DRF action
MCP tool name
GET /api/item/list
item_listPOST /api/item/create
item_createGET /api/item/{id}/retrieve
item_retrievePUT /api/item/{id}/update
item_updatePATCH /api/item/{id}/partial_update
item_partial_updateDELETE /api/item/{id}/destroy
item_destroyNaming uses the model’s lowercase name (same rule as
build_router()).Permissions — no client-side gate. The token’s user hits
ModelViewSet.check_permissions/check_object_permissions; denied actions return 403 over HTTP.
Application code for CRUD:
# djcrud.py
import djcrud_drf
djcrud.site.routes.append(ItemRouter)
from djcrud.permissions import authenticated
djcrud.permissions.add_perm(ItemRouter, "view,add,change,delete", check=authenticated)
djcrud_drf.site.register(ItemViewSet)
That is the full MCP CRUD setup. Custom @action methods use the method name
as the permission shortcode (publish → publish rule).
MCP profiles¶
Declare a McpProfile on the Django host and register it on
djcrud_mcp.site. Registration is required for every stdio MCP client —
remote subprocesses fetch the built profile over HTTP and never synthesize one
locally:
import djcrud_mcp
class ExampleMcp(djcrud_mcp.McpProfile):
viewsets = (ItemViewSet,) # or models=(Item,)
djcrud_mcp.site.register(ExampleMcp)
Profile build lifecycle¶
Same model as HTML routes and DRF ViewSets:
Declare — class attributes on
McpProfile(key,viewsets, optional overrides).Register —
djcrud_mcp.site.register(ExampleMcp)stores the class.Build —
site.build()callsExampleMcp().build(), resolving ViewSet prefixes once and caching them on the instance.Serve —
GET /api/mcp/profiles/{key}/returnsprofile.to_dict()for remotedjcrud-clientsubprocesses.
Computed fields (@property unless overridden on the class):
Field |
Default rule |
|---|---|
|
|
|
|
|
|
|
Derived from registered ViewSets at build time |
|
Same as |
Register one profile per project. Set viewsets / models to list the API
surfaces agents may call.
Custom (non-CRUD) endpoints¶
Only non-standard routes need explicit schema surfacing:
Standalone
APIView(workflow steps, probes)@actionon a ViewSet for one-off operations
Use @extend_schema so the path appears in /api/schema/, then include the
ViewSet (or api_prefixes) on the relevant McpProfile. There is no
parallel client-side tool registry — schema is the single source of truth.
Authentication¶
Same as djcrud_api:
Subprocess:
DJCRUD_TOKENin env (host callsToken.generate)Dev CLI:
POST /api/login/or--user/--password
Passwords are never sent per tool call.
Package layout¶
Host package (djcrud wheel, src/djcrud_mcp/):
src/djcrud_mcp/
site.py # McpSite — register(McpProfile), build profiles
profiles.py # McpProfile (instances built on site.build())
api_viewsets.py # GET /api/mcp/profiles/ (host only)
viewsets.py # discover registered ModelViewSets, api_path_for(model)
Client package (djcrud-client, no Django):
djcrud-client/src/djcrud_client/
profile.py # fetch profile JSON from host
schema.py # filter schema paths by profile prefixes
tools.py # tool_name(model, action), render_path, split_arguments
server.py # create_mcp_server()
api.py # CrudApi, fetch_profile()
config.py