Metadata-Version: 2.1
Name: drf_react_by_schema_mcp
Version: 0.2.1
Summary: Generic, schema-driven MCP server for drf-react-by-schema projects.
Home-page: https://gitlab.com/eita/drf-react-by-schema/drf-react-by-schema-mcp
Author: Cooperativa EITA
Author-email: coletivo@eita.org.br
License: GNU General Public License version 3
Classifier: Environment :: Web Environment
Classifier: Framework :: Django
Classifier: Framework :: Django :: 3.2
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Internet :: WWW/HTTP
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
Requires-Python: >=3.10
Description-Content-Type: text/x-rst
License-File: LICENSE

.. image:: https://gitlab.com/eita/drf-react-by-schema/drf-react-by-schema-mcp/-/raw/master/banner_drf-react-by-schema-mcp_2171x345.png
  :width: 1280
  :alt: 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 <https://opencode.ai>`_ through a small set of generic tools —
``consultar``, ``resumo``, ``criar``, ``editar``, ``minha_conta`` — plus the
schema-aware ``knowledge`` tool and optional per-project shortcuts, so an
assistant can **understand** the project (models, fields, endpoints,
conventions) 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
<https://pypi.org/project/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 <https://pypi.org/project/drf-react-by-schema/>`_) —
  generates the DRF API (serializers, viewsets, filters, schemas) from your
  models;
* **drf-react-by-schema-react** (`npm <https://www.npmjs.com/package/drf-react-by-schema>`_) —
  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 <https://modelcontextprotocol.io>`_ 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:

   .. code-block:: bash

       pip install drf-react-by-schema-mcp

2. Add the app (after the OAuth package) and include the URLs:

   .. code-block:: python

       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):

   .. code-block:: python

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

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``:

  .. code-block:: python

      # 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:

  .. code-block:: bash

      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):

.. code-block:: json

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

With `opencode <https://opencode.ai>`_ that is ``opencode mcp auth example``,
followed by ``opencode mcp debug example``.

Tools
-----

The generic tools mirror the DRF API. List endpoints return the ``results`` list;
detail/object endpoints return the object; ``metadados=True`` returns the full
``{count, next, previous, sum_rows, results}`` envelope.

.. list-table::
   :header-rows: 1

   * - Tool
     - What it does
   * - ``knowledge(modelo?)``
     - The project schema: a compact overview (endpoints, conventions, stamp,
       project ``about``) by default, or one model's fields/interface/enums/
       ``docstring`` with ``modelo``. Registered only when ``KNOWLEDGE`` is on.
   * - ``minha_conta()``
     - ``GET /api/my_account/`` — the logged-in account.
   * - ``consultar(caminho, parametros?, metadados?)``
     - ``GET /api/...`` (list or object). ``parametros`` is the querystring.
   * - ``resumo(caminho, campos?, parametros?)``
     - ``{count, sum_rows}`` 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`` (100 rows/page),
``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 ``__``.

.. code-block:: python

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

    # count + sums for the whole filter, without the list
    resumo("/api/movimento/", ["valor", "valor_bruto"],
           {"field": "data", "operator": "entre",
            "value": "2026-01-01,2026-12-31"})

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 <https://pypi.org/project/drf-react-by-schema/>`_, the base
package's "Machine-readable schema" section), with the caller's Bearer token:

* ``knowledge()`` — a **compact overview**: every endpoint
  (``modelo``/``caminho``/``tipo``/verbose names/parent/related), 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="municipio")`` — that model's field ``schema`` (types,
  ``choices``, ``required``, ``read_only``...), its TypeScript ``interface``,
  the referenced ``enums`` and the model class ``docstring``. Field
  documentation travels inside the ``schema`` as each field's ``description`` —
  distinct from ``help_text``, which is the text the React UI shows the end
  user. Both are opt-in on the Django side (a model docstring, or
  ``description="..."`` passed to a schema field) and trimmed when served. The
  name is matched in any case/style, as a model name or an ``/api/...`` path.

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.
``"valor: campo obrigatório ausente."``). A valid payload is forwarded unchanged.

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:

.. code-block:: python

    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.

.. list-table::
   :header-rows: 1

   * - Key
     - Default
     - Meaning
   * - ``WRITE``
     - ``False``
     - Register the write tools (``criar``/``editar``).
   * - ``KNOWLEDGE``
     - ``True``
     - Register the schema-aware ``knowledge`` tool 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.
   * - ``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.
   * - ``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
-------

GPLv3 — Cooperativa EITA.
