Skip to main content
DRF And React By Schema - MCP

DRF And React By Schema - MCP

A generic, schema-driven MCP server for projects built with DRF And React By Schema (DRBS).

It exposes any DRBS project’s DRF API (/api/...) to MCP clients such as opencode through a small set of generic tools — consultar, resumo, criar, editar, minha_conta — plus the schema-aware knowledge/guia tools and optional per-project shortcuts, so an assistant can understand the project (models, fields, endpoints, conventions, reverse relations) and then read (and, when opted in, write) its data with no per-model tool explosion.

The server is a stateless proxy: it takes the caller’s OAuth 2.1 Bearer token (issued by drf-react-by-schema-oauth), forwards it to the project’s /api/... and lets the project’s own DRF permissions scope the data. It stores no credentials.

About the suite

drf-react-by-schema is a suite that builds headless Django + React applications directly from your models:

  • drf-react-by-schema-django (PyPI) — generates the DRF API (serializers, viewsets, filters, schemas) from your models;

  • drf-react-by-schema-react (npm) — consumes those schemas to build the React forms, grids and CRUD interfaces;

  • drf-react-by-schema-oauth — OAuth 2.1 authorization/resource server that issues the tokens both the API and this MCP server accept;

  • drf-react-by-schema-mcp (this package) — the generic MCP server that exposes the same API to MCP clients.

About this package

A DRBS project already exposes a rich DRF API generated from its models. This package mounts a FastMCP server that proxies that API to MCP clients, so an assistant can:

  • understand the system before acting (knowledge) — the project’s endpoints, conventions and per-model field schemas, fetched from the base package’s GET /drbs/schema/;

  • read any /api/... endpoint (consultar), count and sum filtered sets without downloading them (resumo) and identify the logged-in account (minha_conta);

  • write — opt-in per project — with criar (POST) and editar (PATCH); DELETE is never exposed.

It is deliberately generic: the tool surface is bounded by a fixed set of generic tools plus any project shortcuts, not one tool per model. The same OAuth token authenticates /mcp and /api/..., and the project’s DRF permissions and OAuth scopes (read/write) remain the authority.

Compatibility

The base DRBS package supports Django ≥ 3.2, and some deployments are still on Django 3.2. This package therefore supports Django 3.2 through 5.x (and the 2.x django-oauth-toolkit line pulled in by drf-react-by-schema-oauth). It requires Python ≥ 3.10 (the mcp SDK).

Pre-requisites

  • Django ≥ 3.2

  • djangorestframework

  • drf-react-by-schema-oauth (installed automatically) — provides the OAuth server and the DRF authentication/scopes

  • mcp (installed automatically)

Quick start

  1. Install:

    pip install drf-react-by-schema-mcp
  2. Add the app (after the OAuth package) and include the URLs:

    INSTALLED_APPS = [
        ...
        'oauth2_provider',
        'drf_react_by_schema_oauth',
        'drf_react_by_schema_mcp',
    ]
    
    urlpatterns = [
        ...
        path('', include('drf_react_by_schema_oauth.urls')),
        path('', include('drf_react_by_schema_mcp.urls')),  # Protected Resource Metadata
    ]
  3. Mount the MCP endpoint (see Mounting /mcp).

  4. Configure (all keys optional):

    DRBS_MCP = {
        'WRITE': False,       # read-only by default
        'KNOWLEDGE': True,    # schema-aware knowledge()/guia() + write validation
        'RESOURCE_URL': '/mcp',
        'SHORTCUTS': [],      # dotted paths to project-specific tools
        'EXTRA_QUERY_PARAMS': [],  # querystring params this project reads itself
    }

Mounting /mcp

/mcp speaks the MCP streamable-http transport, a long-lived ASGI application (it cannot be served from a synchronous WSGI URLconf). There are two supported deployments:

  • Combined — one ASGI process serves both Django and /mcp:

    # asgi.py
    from drf_react_by_schema_mcp.server import get_mcp_asgi_application
    
    application = get_mcp_asgi_application()
  • Standalone — a second ASGI process serves only /mcp (point DRBS_MCP['API_URL'] at the Django host) and the reverse proxy routes /mcp to it:

    python -m drf_react_by_schema_mcp.server
    # or: uvicorn drf_react_by_schema_mcp.server:get_mcp_asgi_application --factory

In both cases the endpoint is protected: an unauthenticated request answers 401 with WWW-Authenticate: Bearer resource_metadata="…", so an MCP client can discover the OAuth server (RFC 9728) and start the flow automatically.

Connecting an MCP client

Point a remote MCP client at https://<host>/mcp with OAuth (no secret file):

"example": {
  "type": "remote",
  "url": "https://<host>/mcp",
  "enabled": true,
  "oauth": {}
}

With opencode that is opencode mcp auth example, followed by opencode mcp debug example.

Tools

The generic tools mirror the DRF API. A list endpoint returns {"results": [...]} — one JSON object, never a bare list (a bare list would be split into one MCP content block per record) — while a detail/object endpoint returns the object and metadados=True returns the full {count, next, previous, sum_rows, facets, results} envelope. An idempotent upstream request (GET) is retried once when the connection drops mid-flight.

Tool

What it does

knowledge(modelo?, campos?, detalhe?, sum_rows?, app?, search?, limit?)

The project schema: a compact overview (endpoints, conventions, stamp, project about) by default — narrow it with app/search/limit — or one model’s field schema, its reverse relations (related) and its query capabilities (query) with modelo. detalhe="resumo" (default) keeps only the semantics; campos projects the schema fields and/or selects response sections (schema, query, related, endpoint, sum_rows) — e.g. campos=["query"]; sum_rows=True adds the valid measures/dimensions (large, so opt-in). Registered only when KNOWLEDGE is on.

guia(tema?)

The workflow, ready-made recipes and common mistakes, as data — read it before building queries (tool descriptions are truncated by some harnesses, so the workflow cannot live only there). tema picks a section; an unknown theme returns the whole guide with a warning instead of failing. Registered only when KNOWLEDGE is on.

minha_conta()

GET /api/my_account/ — the logged-in account.

consultar(caminho, parametros?, metadados?, page_size?, filtros?, incluir?, campos?, fields?)

GET /api/... (list or object). parametros is the querystring; filtros=[{campo, operador, valor}] is the ergonomic grid filter; incluir=["<relacao>"] nests each record’s related rows; campos=["a", "b"] (alias fields=, also accepted inside parametros) projects each record onto those fields — narrow a large payload without touching the API.

resumo(caminho, campos?, parametros?, filtros?)

{count, sum_rows, facets} for the whole filtered set, without the list.

criar(caminho, dados)

POST /api/... (registered only when WRITE is on).

editar(caminho, registro_id, dados)

PATCH /api/.../<id>/ (registered only when WRITE is on).

Accepted querystring parametros (combinable): page/page_size, search (text), ordering (- for descending), sum_rows (comma-separated numeric fields), and the grid filter field / operator / value. The three filter keys may each be a list; items match positionally and combine with AND. Common operators: equals, contains, startsWith, endsWith, isAnyOf, is, not, after, before, onOrAfter, onOrBefore, entre ("start,end"), isEmpty, isNotEmpty. field accepts relation paths with __. Equality on a foreign key compares the related object’s label (__str__/nome/slug, the query.label_field), not its id; to filter by id use field="<fk>__id" with operator="is".

consultar/resumo also accept the structured filtros, which builds the three lists for you and is the form to prefer:

consultar("/api/retirada/", filtros=[{"campo": "cooperada__id",
                                      "operador": "is", "valor": 1}])

Because DRF silently ignores a querystring param it does not understand, the read tools reject an unknown one instead of returning unfiltered rows: the error names the endpoint’s valid filter_fields and the correct grid-filter form (a foreign key passed as a plain param, e.g. {"cooperada": 1}, is the classic trap). A project whose viewsets read their own params declares them in DRBS_MCP['EXTRA_QUERY_PARAMS']. Pass field or filtros, not both.

A grid filter’s field/operator is validated on both forms — filtros=[{campo, operador, valor}] and the raw parametros={"field": ...} — so an unknown field fails the same way whichever you use (the API would drop it and return every row).

An ordering term is validated the same way, against the endpoint’s query.ordering_fields: DRF drops an unknown term silently (ordering=-id on a model that does not order by id quietly keeps the default order), so the MCP rejects it and lists the fields the endpoint accepts.

consultar can also nest a record’s related rows with incluir: it reads the model’s reverse relations from the schema (related) and attaches the child rows under the relation name, so one call replaces a manual “find the child endpoint, then filter it by <fk>__id”:

# a retirada and its hour records, in one call
consultar("/api/retirada/6900/", incluir=["projetos"])

It costs one request per record per relation, so it is meant for a record (or a small page): above DRBS_MCP['MAX_INCLUIR'] records (default 25) the call is refused rather than fanning out, and a relation with more rows than one page raises instead of silently truncating. knowledge(modelo="Retirada") lists the available relation names under related.

sum_rows items may also carry facets: medida|dimensao[|dimensao...] [|limite] (|-separated). medida is a numeric field, count (COUNT(*), i.e. rows) or count_distinct:<campo> (COUNT(DISTINCT campo), distinct non-null values); each dimensao must be a foreign key, a field with choices or a boolean — and may be a nested forward relation with __ (e.g. categoria__regiao); a trailing all-digits part is the top-N limite (ordered by the measure). Each measure still reports its <medida>_total flat (with : replaced by _), and the grouped buckets come back under facets. Use count_distinct:<campo> to count entities (e.g. distinct parents) when a model has several rows per entity.

# one page of 2026 entries
consultar("/api/widget/",
          {"field": "publicado_em", "operator": "entre",
           "value": "2026-01-01,2026-12-31", "page": 1})

# count + sums for the whole filter, without the list
resumo("/api/widget/", ["preco", "peso"],
       {"field": "publicado_em", "operator": "entre",
        "value": "2026-01-01,2026-12-31"})

# nº de itens por categoria (e total de preço)
resumo("/api/widget/", ["preco", "count|categoria"])

# nº de categorias distintas por tipo (entidades, não linhas)
resumo("/api/widget/", ["count_distinct:categoria|tipo"])

# top-5 categorias por preço, tabela cruzada com tipo
resumo("/api/widget/", ["preco|categoria|5", "count|categoria|tipo"])

# nº de itens por região da categoria (relação aninhada)
resumo("/api/widget/", ["count|categoria__regiao"])

Schema awareness

Instead of guessing /api/<model>/ paths and field names, the assistant can ask the project what it looks like. knowledge reads the base package’s machine-readable schema endpoint, GET /drbs/schema/ (see drf-react-by-schema, the base package’s “Machine-readable schema” section), with the caller’s Bearer token:

  • knowledge() — a compact overview: every top-level endpoint (modelo/app_label/caminho/verbose names — narrow it with app=/search=/limit=, since a large project’s list is the biggest payload), the API conventions (pagination, ordering/search/sum_rows, grid-filter operators), the schema stamp and the project’s about (the project root’s ABOUT.md, when present).

  • knowledge(modelo="widget") — that model’s field schema (types, choices, required, read_only…, with related_model on each relation field), its query capabilities (query.filter_fields — what the grid filter accepts as campo/field —, query.label_field and query.search_fields/query.ordering_fields) and its reverse relations (related: the child endpoint, the linking field and the nested_path when one exists), so the assistant can find the rows that hang off a record instead of guessing an endpoint name. The valid sum_rows measures and dimensions (medidas/dimensoes/medidas_distintas) come only with sum_rows=True (the block is large). detalhe="resumo" (the default) keeps only each field’s semantics; detalhe="completo" adds the per-field documentation (description/help_text) and campos=[...] projects the schema onto the named fields or selects whole response sections (schema/query/related/endpoint/sum_rows). The name is matched in any case/style, as the endpoint name, the underlying Django model name, or an /api/... path.

  • guia() — the workflow as data: the conventions, the canonical loop (knowledge → knowledge(modelo) → consultar/resumo), ready-made recipes (last record; filter a relation by id or by label; range; aggregate by relation; expand a record’s related rows; discover sum_rows measures) and the common mistakes. guia(tema=...) returns one section; an unknown theme returns the whole guide with a warning rather than failing. It exists because a tool description is truncated by some harnesses (only the first paragraph may reach the model), so anything an assistant must know has to be reachable by a call.

The bundle is schema-only: the React-only TypeScript artifacts (interface/enums), the React-only field keys (ui_required, model_required, style…) and the auto-generated nested *Related* viewsets are deliberately not shipped — the reverse relations and the query capabilities above carry what an assistant needs instead.

The bundle is fetched once per MCP process and cached in memory, keyed by its stamp: later calls send If-None-Match and a 304 Not Modified reuses the cache, while a changed schema (new stamp) transparently replaces it. Nothing is written to disk and the schema is never exposed as an MCP resource — it reaches the agent only as knowledge() output.

When the project exposes /drbs/schema/, criar/editar validate the payload against the same schema before sending it — required fields, read_only fields, value types and choice options — and return a field-level error without calling the API if it is invalid (e.g. "preco: campo obrigatório ausente."). A valid payload is forwarded unchanged.

A relation (a field carrying related_model) is accepted even though its serializer field is read_only, because DRBS writes it from the raw body; only an existing record’s id is allowed — the inline-create form ({"label": ...} or a tmp* id) is refused, since it would create a new row or silently null a non-creatable relation.

If the project does not expose the endpoint (drf-react-by-schema < 0.27 or SCHEMA_ENDPOINT off), knowledge reports it as unavailable and writes are not validated — the server keeps working as a plain proxy.

Shortcuts

Register project-specific tools with dotted paths; each is a callable taking the MCP Context first (exactly like the generic tools) and is exposed under its own name:

DRBS_MCP = {
    "SHORTCUTS": ["myproject.mcp.saldo_ferias"],
    # optionally limit/rename the surface:
    "TOOLS": ["consultar", "resumo", {"ferias": "saldo_ferias"}],
}

Settings

DRBS_MCP is a single dict; every key is optional.

Key

Default

Meaning

WRITE

False

Register the write tools (criar/editar).

KNOWLEDGE

True

Register the schema-aware knowledge/guia tools and validate write payloads against /drbs/schema/ when the project exposes it.

SCHEMA_PATH

"/drbs/schema/"

Path (or absolute URL) of the base package’s schema bundle endpoint.

EXTRA_QUERY_PARAMS

[]

Querystring params this project’s endpoints legitimately read themselves (e.g. in a filter_by_callback). The read tools reject unknown params, so a project that reads its own must declare them here.

MAX_INCLUIR

25

Max records consultar(..., incluir=[...]) expands in one call (one request per record per relation). Above it the call is refused.

TOOLS

None

Allowlist of tools to expose. None = all generic (minus writes when WRITE is off) plus every shortcut. A list limits and orders the surface; an entry may rename, e.g. {"buscar": "consultar"}.

SHORTCUTS

[]

Dotted paths to project-specific tool callables.

RESOURCE_URL

"/mcp"

Path (or absolute URL) of the protected resource.

API_URL

None

Base URL the proxy forwards to. None = this request’s origin (the right value when combined with Django); set it for a standalone process.

SERVER_NAME

"drf-react-by-schema"

Name advertised to MCP clients.

READ_SCOPE / WRITE_SCOPE

"read" / "write"

Scope names advertised in the Protected Resource Metadata.

VALIDATE_TOKEN

True

Validate the Bearer token against VALIDATE_PATH before handling a request (so an expired token yields 401).

VALIDATE_PATH

"/api/my_account/"

API path used for that check.

ALLOWED_HOSTS

[]

Transport Host allowlist (anti-DNS-rebinding); empty disables it.

STATELESS_HTTP

True

Serve streamable-http without session state (a fresh transport per request), so a client reconnect after a restart never hits 404 Session not found. Set False only if a client needs server-initiated messages.

HOST / PORT

"127.0.0.1" / 8000

Bind address for the standalone entrypoint.

TIMEOUT

60

Upstream request timeout, in seconds.

Security model

  • Stateless proxy — no credentials are stored server-side; the caller’s Bearer token is forwarded to /api/... on every call.

  • Read-only by default — writes require WRITE=True and a token carrying the write scope and the project’s DRF permissions.

  • No ``DELETE`` — only GET/POST/PATCH are exposed.

  • Schema is structure, not data — /drbs/schema/ carries the generated picture (models, fields, endpoints, static config) only: no row data and no secrets; it is fetched with the caller’s own token and the project’s existing auth.

  • Scope enforcement is the project’s: read for safe methods, write for unsafe ones, via drf_react_by_schema_oauth.permissions.HasScope.

License

AGPLv3 — Cooperativa EITA.

Metadata

Release files for drf-react-by-schema-mcp 0.3.6

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for drf-react-by-schema-mcp 0.3.6
File Size Uploaded Provenance Actions
drf_react_by_schema_mcp-0.3.6.tar.gz 642.1 kB With provenance Details for drf_react_by_schema_mcp-0.3.6.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for drf-react-by-schema-mcp 0.3.6
File Interpreter ABI Platform Actions
drf_react_by_schema_mcp-0.3.6-py3-none-any.whl Python 3 none any Details for drf_react_by_schema_mcp-0.3.6-py3-none-any.whl

Total release size: 730.4 kB

Release files / drf_react_by_schema_mcp-0.3.6.tar.gz

Download URL drf_react_by_schema_mcp-0.3.6.tar.gz
Size 642.1 kB
Tags Source
SHA-256 checksum
How to use checksums
29ab02053aaf809edae30915071fedc2265a8188df583d01937a8a17633a94fe
BLAKE2b-256 checksum
How to use checksums
de444f8c61c55584b8a7e43b71fd888a60eca98e878cc9a7e96e8ad50ed30485
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.12.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitLab CI/CD, verified by PyPI on Nov 8, 2026.

Transparency log

Release files / drf_react_by_schema_mcp-0.3.6-py3-none-any.whl

Download URL drf_react_by_schema_mcp-0.3.6-py3-none-any.whl
Size 88.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
9975fcfa0a376445f5cf291e818ae3570e01804ac631f67d47eb9c8b38ee850a
BLAKE2b-256 checksum
How to use checksums
53b3b26f5e7a3cd9e877497f3f90d8164acad4068db1083d98d2e417aa7a086a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.12.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitLab CI/CD, verified by PyPI on Nov 8, 2026.

Transparency log

Security for drf-react-by-schema-mcp 0.3.6

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

Both files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for drf_react_by_schema_mcp-0.3.6.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitLab CI/CD, verified by PyPI on Nov 8, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page