Relations
How 0-mcp exposes ForeignKey, OneToOne, ManyToMany and reverse relations on list and detail views.
listrelatedfields
Drives select_related() on the list query and shapes nested objects in the response.
class UserResource(BaseResource):
model = User
list_fields = ['id', 'email']
list_related_fields = {
'account': ['id', 'name'],
'account__plan': ['id', 'tier'],
}The list response includes:
{
"id": 1,
"email": "[email protected]",
"account": {"id": 7, "name": "Acme", "plan": {"id": 2, "tier": "pro"}}
}Chained relations (account__plan) are resolved in one query via select_related. Use them aggressively to avoid N+1.
editrelatedfields
Same shape, applied to detail (GET /resource/{id}):
edit_related_fields = {
'account': ['id', 'name', 'plan__tier'],
}When the related field is a many-to-many, 0-mcp emits a separate query per relation and exposes a list:
edit_related_fields = {
'tags': ['id', 'name'],
}Prefetch (one-to-many / reverse FK)
Use list_prefetch_related and edit_prefetch_related for collections:
class UserResource(BaseResource):
list_prefetch_related = {
'orders': ['id', 'total', 'created_at'],
}Each row in the list will have an orders array. Internally the library calls prefetch_related('orders') and iterates values(*fields) async-ly.
Many-to-many on writes
POST/PATCH bodies can include m2m fields by name. The library detects them on model._meta.many_to_many and calls save_through_model(obj, field_name, ids):
๐ค MCP โ tools/call
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "create_space",
"arguments": {"name": "Demo", "tags": [1, 2, 3]}
}
}๐ REST โ POST /spaces
{"name": "Demo", "tags": [1, 2, 3]}On PATCH, m2m values use aset(value) on the manager โ replacing the existing set.
Foreign keys on writes
Writes accept either the FK column name (account_id) or the relation name (account). When the relation name is used and the value is a dict, the library reduces it to its id automatically โ same coercion on both surfaces.
๐ค MCP โ tools/call
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "update_user",
"arguments": {"id": 1, "account": {"id": 7, "name": "ignored"}}
}
}๐ REST โ PATCH /users/1
{"account": {"id": 7, "name": "ignored"}} # only id is used
{"account_id": 7} # equivalent
{"account": 7} # equivalentAuto-fill from session
When the model has any of these columns, POST auto-populates them from self.user:
| Column | Filled with |
|---|---|
created_by | user.id |
updated_by | user.id |
owner | body.get('owner_id', user.id) โ the body wins if it set one |
Auto-fill only triggers when an authenticated user is present. Public endpoints (authenticated = False) skip it.
0-mcp by Stamatios Stamou Jr โ github.com/ssjunior/0-mcp