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}                                 # equivalent

Auto-fill from session

When the model has any of these columns, POST auto-populates them from self.user:

ColumnFilled with
created_byuser.id
updated_byuser.id
ownerbody.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