Releases: MartinCastroAlvarez/django-admin-mcp-api
Release list
v1.5.0
Changed
- Bumped the
django-admin-rest-apifloor to^1.7.0, which replaces the
legacy-iframe path with server-renderedhtml-fragmentform-spec payloads
for ModelAdmins that render a custom template
(#84).
Added
custom-templatediscriminator forchange_form_templateadmins
(#84).
When rest-api's shared form-spec resolver renders a custom template (a
declaredchange_form_template/add_form_template, or a request-driven
change_viewoverride) it returnsrenderer: "html-fragment"— opaque HTML
an MCP client can't introspect or drive.admin.form_specnow renames that
single upstream signal to a clear non-driveable discriminator:
{ "renderer": "custom-template", "reason": "ModelAdmin override: change_form_template", "legacy_url": …, "spa_url": …, "machine_driveable": false }.legacy_urlis
the upstreamsubmit_url;spa_urlis derived by reusing rest-api's
map_redirect_to_spa(the/admin/→SPA_URL_PREFIXswap), so detection is
never duplicated here.admin.form_submitagainst such a form now refuses
to submit — it does not fabricate field values or forward a POST — and returns
{ "ok": false, "reason": "custom-template", "message": "… not programmatically driveable …" }
(isError: true, status422). The pre-1.7.0legacy-iframebranch is
dropped: MCP clients never iframed anything, so it was never meaningful here.
Documentation
tools-reference.md: documentedadmin.form_spec/admin.form_submit
(previously undocumented) and thecustom-templatediscriminator + refusal.api-contract.md §4.4: documented the one narrow exception to the
pure-pass-through contract (thehtml-fragment→custom-templaterename).threat-model.md §3: added the "agent fabricating field values for an
opaque custom-template form" row (mitigated by the discriminator + refusal).
v1.4.0
Changed
- Bumped the
django-admin-rest-apifloor to^1.6.0, which adds the
JSON 405 envelope, prepopulated/autocomplete hints, gettext i18n, and
security logging. The MCP wire forwards rest-api's payloads unchanged, so
no tool behaviour changes here — but pinning the floor guarantees the
legacy-iframediscriminator (shipped in rest-api 1.5.0) is present on a
freshpoetry install, keeping the integration suite green
(#75).
Added
- Defense-in-depth schema bounds on primary keys (#79).
The sharedPKstring schema now carriesminLength/maxLength(256) and
a^[^/?#]+$pattern that forbids path-structural characters, and thepks
arrays inadmin.action/admin.bulk_updategainmaxItems(1000). Typo'd
or oversized inputs now fail with a preciseINVALID_PARAMSjson-pointer
instead of forwarding to rest-api. - Discovery-side structured logging (#80).
initialize,tools/list, the GET landing, andGET /manifest/now each
emit one{user, method, status}INFO log (mcp.method), closing the
forensic gap where the full capability surface could be enumerated with no
audit trail. Request bodies are still never logged. initializeprotocol-version negotiation (#81).
The server now readsparams.protocolVersion; a divergent value is logged
(mcp.initialize.protocol_mismatch) and the single supported version is
still returned, laying groundwork for future multi-version support.- Doc-fidelity test asserting the README "The N tools" heading equals
len(tools.all_tools())so the documented count can't drift again
(#76).
Changed (internal)
- Memoised the immutable tool catalogue (#82).
tools_catalogue()now caches its rendered list keyed onDISABLED_TOOLS
rather than rebuilding 18 manifest entries on everytools/list, GET/,
andGET /manifest/. The cache is the globally-shareable base catalogue.
Documentation
- README: corrected the "The 16 tools" heading to "The 18 tools" and
refreshed the stale dev stats (120 tests / 95% coverage)
(#76). - Static tool catalogue is now explicitly documented as not
permission-filtered inapi-contract.md §4.2andthreat-model.md §4.1
(presence ≠ permission; authz is enforced per call inside rest-api). Per-user
filtering was evaluated and intentionally declined: tools are model-generic
verbs, the per-user model axis is already answered byadmin.registry, and
any other filter would re-derive permissions locally — forbidden by the prime
directive (#77). admin.listdocumented as an intentional changelist passthrough in
tools-reference.mdand the tool description: unknown keys forward verbatim
as query params and are not schema-caught, mirroring the admin changelist
querystring (#78).- Synthetic-request attribute contract promoted out of inline comments
indispatch.pyintoARCHITECTURE.md(which attributes cross the seam,
which are deliberately skipped, and why middleware is not re-run)
(#82).
v1.3.0
Changed
- Bumped the
django-admin-rest-apifloor to^1.5.0, which broadens
the form-speclegacy-iframedetection to request-driven custom views
(achange_view/add_viewoverride that renders a non-standard template,
not just thechange_form_templateattribute). No MCP code changes — the
wire adapter forwards rest-api's payload unchanged — soadmin.form_spec
now surfacesrenderer: "legacy-iframe"for those views too, letting a
caller know the form is not machine-driveable instead of inventing field
values (#70).
Added
Jobcross-repo fixture + end-to-end tests. The test project now
carries the sameJobAdminfixture (request-driven customchange_view)
and mounts the legacy admin, so an integration test asserts the MCP wire
forwards both answers unchanged: Path A (?absent) →form-specwith the
large-textareametadatawidget; Path B (query={"run_custom": "1"}) →
renderer: "legacy-iframe"with the legacy URL preserved.
v1.2.0
Added
admin.form_spec+admin.form_submittools (#70).
admin.form_spec(app_label, model_name, pk=None, query={})returns the
ModelAdmin-resolved form for a row (or the add page whenpkis
omitted) — request-awareget_form/get_fieldsets/
get_readonly_fields, with each field's resolved widget mapped to a
closedwidget.kindenum.admin.form_submit(…, data, query={})
re-runsis_valid()server-side through the same resolved form, so
request-aware validation is identical across MCP, the SPA, and the
legacy admin. Both forward through to rest-api'sform-specendpoint
and write views, so the payload is byte-identical to the SPA's — one
resolver, no drift.
Changed
- Bumped the
django-admin-rest-apifloor to^1.4.0, which ships
theform-specendpoint the two new tools forward to (#70).
Fixed
__version__no longer drifts from the packaged version (#62).
It is now derived from the installed package metadata via
importlib.metadata.version, somanifest.server_info()(and the MCP
initializeblock) always advertise the real release version instead
of a hand-maintained string that had fallen to1.0.4.
Changed
- Consolidated the Python lint stack on Ruff + mypy + bandit (#63).
Removed Black, standalone isort, flake8, and pylint (their config
blocks, dev dependencies, pre-commit hooks, and CI steps). Ruff now
owns linting, formatting, and import sorting (I); the lockfile,
scripts/lint.sh, and the docs are updated to match. - Enabled
mypy --strict(#64). Fixed the twono-any-return
errors at the dispatch seam (server/dispatch.py) by casting the
resolver view callable and the syntheticRequestFactoryrequest to
their concrete Django types. - Future-proofed the dispatcher except clause (#67). Dispatcher
exceptions now share aDispatchErrorbase; the view catches that
base so any new dispatcher failure mode maps toSERVER_ERROR_UPSTREAM
instead of escaping to a Django 500.
Removed
- Deprecated
default_app_configfrom the package__init__(#65).
It was removed in Django 4.1 and this package supports Django >=4.2,
whereapps.pyis auto-discovered. - Inert
# noqadirectives (A003,D401) that suppressed rules no
configured tool enforces (#68); the explanatory text is kept as plain
comments.
v1.1.0
Added
- Django 4.2 LTS support (django-admin-react#622).
The pin is relaxed fromdjango >=5.0,<7.0todjango >=4.2,<7.0;
the CI matrix exercises 4.2 alongside 5.0 / 5.1 / 5.2 across Python
3.10–3.13 (3.13 excluded for 4.2 conservatively since pip may
resolve to a pre-4.2.16 release). Framework :: Django :: 4.2classifier.
Changed
django-admin-rest-apiconstraint^1.0.11→^1.1.0to pick
up the API package's own Django 4.2 support and its
SimpleListFiltervalue-shape normalisation. Supersedes the
^1.0.6→^1.0.11bump from 1.0.4 — 1.1.0 is a strict
superset.
Why a minor bump
New supported environment (Django 4.2 LTS) per SemVer's "additive
features that broaden compatibility" guideline. No behaviour change
for consumers already on 5.0+; the MCP package forwards everything
to django-admin-rest-api and adds no Django logic of its own.
v1.0.4
Changed
django-admin-rest-apiconstraint floor raised^1.0.6→^1.0.11.
Pulls in the upstream improvements since 1.0.6: actions runner caps
pk-list length, history view redacts a denylist of field names
(1.0.7);PanelEndpointsMixindeprecated in favour of declaring
panels = {...}directly onModelAdmin(1.0.8); rest-api's own
system checks for settings hygiene (1.0.9); N+1 perf + custom-user-
model safety (1.0.11). URL surface is unchanged across the range;
the MCP dispatcher forwards to the same paths.admin.paneltool description updated — no longer mentions
PanelEndpointsMixin. New copy describes the plain-Django
panels = {"name": "method_name"}attribute approach; the mixin
is now a deprecated no-op shim in rest-api and is kept only as a
historical parenthetical so consumers on older rest-api versions
recognise the term.docs/tools-reference.mdpanel section updated to match.
Fixed
- README
serverInfo.versionexample bumped 1.0.2 → 1.0.3 to reflect
the previous shipped release; test count 95 → 96 to match current.
v1.0.3
The "ship the audit fixes" release. 16 audit issues triaged and closed
across six PRs. Two new public settings, one new endpoint shape, one
new URL conf, two new example directories, two new long-form docs,
plus an end-to-end Trusted-Publishing pipeline that no longer needs a
local token to ship a release.
Added
MAX_REQUEST_BYTESsetting (default 256 KiB) — caps/mcp/
POST envelopes well below Django's project-wide 2.5 MiB form-upload
ceiling. Oversized requests return 413 +INVALID_REQUESTbefore
the JSON parser runs.DISABLED_TOOLSsetting — tuple of tool names to suppress from
tools/listand reject intools/call. Read-only deployments
typically set("admin.destroy", "admin.bulk_update", "admin.set_password").manage.py checkintegration. Three hooks:E001(rest-api
missing fromINSTALLED_APPS),E002(ADMIN_SITEdoesn't
resolve),W001(typo inDISABLED_TOOLS).GET /mcp/landing. Content-negotiated — HTML for browsers,
JSON forAccept: application/json. Shows server name, version,
protocol, tool count, and links to the manifest.django_admin_mcp_api.bundle_urls— opt-in one-include URL conf
that auto-mounts rest-api alongside MCP under the consumer's prefix.- Structured logging at
django_admin_mcp_api.server.views
(mcp.auth.*,mcp.tools_call,mcp.tools_call.upstream_error).
Bodies are never logged — only{user, tool, status}. docs/tools-reference.md— one section per tool with the
schema, the forwarded rest-api endpoint, and a worked example.docs/deployment.md— WSGI / ASGI, TLS, CORS, rate-limit,
logging, and health-check recipes.examples/clients/— drop-in Claude Desktop / Cursor / VS Code
MCP config templates.examples/headless-client/— programmatic-login bootstrap +
stdlib-only MCP client for scripts / CI / services.- README "Why two apps?" callout + the agent recipe for
admin.actiondiscovery + Custom AdminSite subsection. - Auto-create GitHub Releases on tag push.
publish.ymlextracts
the CHANGELOG entry, marks pre-releases by version suffix, and
attaches the wheel + sdist.
Changed
- Security:
_dont_enforce_csrf_checksflag no longer forwarded
to the synthetic rest-api request. Latent CSRF-bypass risk closed. - Security:
UnknownRestApiPathandUnsupportedDispatchMethod
now caught into JSON-RPCSERVER_ERROR_UPSTREAMenvelopes instead
of bubbling to Django's 500 handler.
Notes
First release shipped end-to-end through PyPI Trusted Publishing —
no .env token, no manual poetry publish. The Trusted Publisher
was registered on PyPI for this project; the workflow's OIDC token
authenticates every future tag push.
Tests: 81 → 96 passing, 91% → 93% line coverage.
v1.0.2
Changed
django-admin-rest-apiconstraint floor raised from^1.0.0→^1.0.6.
Required to expose the newtargetfield on action descriptors
(batch vs detail). Fresh installs were already on 1.0.6 via the
caret range; this just makes the requirement explicit.admin.actiontool description spells out the new batch/detail
dispatch — agents readingtools/list(oradmin.registry) see
thetargetfield on each action and can decide whether to pass
one pk (detail) or many (batch). The wire endpoint is unchanged;
rest-api dispatches internally based on the action callable's
signature.admin.action.pksschema description notes the per-target
constraint (exactly 1 for detail, ≥1 for batch).
Notes
No code change in the dispatch layer. The new target field flows
through every existing tool that surfaces action descriptors
(admin.registry, admin.list, admin.retrieve) without any
wrapping — rest-api adds the field; this package forwards it. The
74 existing tests still pass; agents that already use admin.action
keep working unchanged for batch actions.
v1.0.1
Changed
- README rewritten in
django-admin-rest-api's visual style — simple
#H1 + blockquote tagline, six-badge row, three-package family
table with emoji-coded rows, and single-emoji section markers
throughout. - Folder READMEs (
django_admin_mcp_api/,server/,tools/,
tests/,scripts/,docs/,examples/) standardised on the
same "tagline → In this folder → What does NOT belong here →
See also" pattern. django-admin-rest-apilockfile pinned to1.0.1.- Top-level README now includes a "Configuration" section surfacing
theDJANGO_ADMIN_MCP_APIsettings namespace.
Added
.github/workflows/publish.yml— auto-publishes to PyPI onv*
tag push via Trusted Publishing. Records the publish as a
deployment under thepypienvironment so it shows in the GitHub
Deployments sidebar.CONTRIBUTING.md"Releases" section documenting the tag-and-push
flow.
Removed
.github/README.md— GitHub was surfacing it on the repo home
page in place of the root README.ALLOW_ANONYMOUSfrom every user-facing doc (README, ARCHITECTURE,
quickstart settings). The setting is now an internal test-only knob;
the security suite scans for it leaking back into docs.
v1.0.0
Added
- JSON Schema input validation on every
tools/call. Arguments are
checked against the tool's declaredinput_schema(Draft 2020-12)
before forwarding to django-admin-rest-api, so malformed calls
surface asINVALID_PARAMSwith the json-pointer path of the
failing field instead of bubbling up as a generic rest-api 400. docs/threat-model.md— MCP-layer threat model (assets, trust
boundaries, mitigations, out-of-scope items, review checklist).docs/api-contract.md— the full MCP wire contract (JSON-RPC
envelope, the three methods, error code vocabulary, semver policy).- GitHub issue templates (
bug_report.yml,feature_request.yml,
config.yml) and a PR template that nudge contributors toward the
right venue and the right information. .github/CODEOWNERSrouting every PR review through the
package maintainer with extra protection on the wire layer and the
.github/folder.
Changed
- Pinned to
django-admin-rest-api ^1.0.0— the upstream REST API
shipped its stable 1.0 at the same time, so the MCP adapter now
requires the matching major. Users on the0.1.0a0alpha must
upgrade to rest-api 1.x before installing this release. - CI matrix swaps Django via
pip installinstead ofpoetry add, so
the Python 3.12+ × Django 6.0 cells now resolve and pass. Development Statusclassifier promoted to5 - Production/Stable.
Notes
This is the first stable release. The wire contract is now covered by
the semver policy in docs/api-contract.md §7 — any breaking change
will be a major version bump with a migration paragraph in this file.