Skip to content

Releases: Streamlined-Analytics/django-stateless-mcp

0.1.4

Choose a tag to compare

@BenA-SA BenA-SA released this 30 Jul 15:04
d888ed9

django-stateless-mcp 0.1.4

Added

  • Subscription streams (SEP-2575). subscriptions/listen — the
    2026-07-28 replacement for the standing GET stream, whose POST response is
    itself the stream — is now served: under ASGI the response streams live SSE
    frames (ack, then filtered, subscription-id-tagged events) until the client
    disconnects; under WSGI, where a live stream would pin a worker, the view
    answers an explicit 501 unsupported_deployment instead of hanging. Routed
    by the spec's required mcp-method header; every other request is
    byte-identical to before. Wire an external SubscriptionBus
    (MCPServer(subscriptions=…)) for multi-replica event fan-out. The
    conformance suite's server-stateless scenario now passes 30/30 and has
    left the expected-failures baseline. (ADR-0020)

Fixed

  • Worker-thread database connections are now recycled. Sync tools run
    in the SDK's thread pool, which Django's request_finished cleanup never
    reaches — connections opened there were never aged out or health-checked,
    a slow stale-connection leak under sustained load. The view now runs
    Django's own close_old_connections inside that pool after each request,
    honouring CONN_MAX_AGE exactly as request threads do. (ADR-0021)

Changed

  • mcp_view's published return annotation widens to HttpResponseBase
    (streaming responses are not HttpResponse subclasses).
  • The example project registers test_trigger_tool_change /
    test_trigger_prompt_change tools and an in-memory subscription bus.

0.1.3

Choose a tag to compare

@BenA-SA BenA-SA released this 30 Jul 12:20
cf0a6ec

django-stateless-mcp 0.1.3

Fixed

  • The view now works in a default Django project: it is CSRF-exempt. Any
    project running the standard CsrfViewMiddleware — which is to say, any
    conventionally configured Django project — rejected every MCP POST with
    403 "CSRF cookie not set". MCP clients authenticate with a bearer header
    an attacker's page cannot set, not ambient cookies, so CSRF does not apply;
    mcp_view now returns a csrf_exempt view (the same posture DRF takes for
    token-authenticated APIs). Caught by CodeQL when the example project gained
    a realistic middleware stack; a regression test now posts through
    Client(enforce_csrf_checks=True).

Changed

  • The example project carries the standard middleware a real project runs
    (sessions, CSRF, auth), so request.user exists on every endpoint —
    AnonymousUser when unauthenticated — instead of a tool touching .user
    crashing with AttributeError on the open endpoints.
  • example/README.md is now a reproduction-grade walkthrough of the full
    manual test plan: MCP Inspector setup (including the Protocol Era gotcha),
    the elicitation round-trip, the permission grant/revoke cycle, and the
    statelessness curl proofs — plus the July 2026 client status: Claude Code
    speaks the stateless transport but not SEP-2322 elicitation yet, so MCP
    Inspector is the reference client.

0.1.2

Choose a tag to compare

@BenA-SA BenA-SA released this 30 Jul 09:22
2e1772c

Warning

Broken in default Django projects — upgrade to 0.1.3. With the standard CsrfViewMiddleware active (any conventionally configured project), every MCP POST is rejected with 403 CSRF cookie not set. 0.1.3 makes the view csrf_exempt (ADR-0018).

django-stateless-mcp 0.1.2

Fixed

  • A single GET request could permanently peg a worker at 100% CPU. The
    view routed every method into the SDK's transport, whose GET handler opens
    an SSE listen stream and waits for client disconnect by polling the ASGI
    receive callable. The bridge's synthesized receive replayed the request
    body forever without ever yielding, so the disconnect listener became a hot
    spin that starved the worker's event loop — one anonymous GET per worker
    was a full denial of service. Two fixes, either sufficient: the view now
    serves POST only (a stateless exchange is one request and one response;
    GET/DELETE session machinery has nothing to attach to) and answers other
    methods with 405 Method Not Allowed; and the synthesized receive now
    follows the ASGI contract — the buffered body once, then http.disconnect.

0.1.1

Choose a tag to compare

@BenA-SA BenA-SA released this 29 Jul 14:07
19bc308

Warning

Denial-of-service defect — upgrade to 0.1.2. A single anonymous GET to any MCP endpoint permanently pegs one worker at ~100% CPU (the SSE disconnect listener spins against the bridge's synthesized receive). Fixed in 0.1.2 (ADR-0017).

django-stateless-mcp 0.1.1

Fixed

  • A bare install could not be imported. 0.1.0's __init__ eagerly
    imported StructlogRequestLogger, whose module imports structlog — an
    optional dependency — so import django_stateless_mcp raised
    ModuleNotFoundError unless the [structlog] extra happened to be
    installed. The logger is now imported lazily (PEP 562), and reaching it
    without structlog raises an ImportError naming the extra to install:
    django-stateless-mcp[structlog]. 0.1.0 has been yanked on PyPI.

0.1.0

Choose a tag to compare

@BenA-SA BenA-SA released this 29 Jul 14:07
a384db8

Warning

Yanked on PyPI — broken import on a bare install. import django_stateless_mcp raised ModuleNotFoundError: No module named 'structlog' unless the optional [structlog] extra happened to be installed. Use 0.1.1.

django-stateless-mcp 0.1.0

The first release: a stateless Model Context Protocol server for Django, built
on the 2026-07-28 spec.

uv add django-stateless-mcp

Features

  • mcp_view(server) — serve an MCPServer as a stateless streamable-HTTP
    Django view, under both WSGI and ASGI. Optional OAuth bearer authentication
    via the SDK's TokenVerifier protocol, with required_scopes.
  • mcp.py autodiscovery — adding django_stateless_mcp to INSTALLED_APPS
    imports each app's mcp module at startup, so apps register tools the way
    they register admin models.
  • request_state_security() — keys the SDK's elicitation-resume encryption
    from SECRET_KEY (and SECRET_KEY_FALLBACKS), so elicitation survives a
    multi-worker deployment instead of failing when a retry lands on another
    worker.
  • django_request(ctx) — reach the authenticated Django request and ORM
    from inside a tool, with no request-spanning global state.
  • StructlogRequestLogger — optional flow-logging middleware
    (django-stateless-mcp[structlog]), one structured event per request.

Supported versions

  • Python 3.12, 3.13, 3.14
  • Django 5.2 LTS and 6.0
  • mcp 2.0.x

Verified against the spec

The official MCP conformance suite (modelcontextprotocol/conformance) runs in
CI against a live server; the full stateless-elicitation scenario set passes.

Contributors

@BenA-SA (Ben Atkinson) created django-stateless-mcp.