Middlewares

Three middlewares ship with 0-mcp. Each does one thing well, runs in a specific order, and stays out of your way once installed.

Recommended order

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    # ... your other middleware ...
    'zeromcp.SecurityMiddleware',     # 1. block scanners early
    'zeromcp.AuthMiddleware',         # 2. resolve session for non-resource views
    'zeromcp.ExceptionMiddleware',    # 3. render HTTPException as JSON
]

Order matters. Security runs first so blocked IPs cannot reach the auth middleware. Auth runs before your views so non-0-mcp views can read the resolved session. Exception runs last so it can catch errors raised by anything above.

Two protection layers

0-mcp uses two related but distinct layers:

Both layers share the same blocked-IP store in Redis (rate_limit:blocked:<ip>), so a block raised by one layer is seen by the other.

SecurityMiddleware

Stops scanners and abusers before any view runs.

On every request, it checks:

When any rule fires, the middleware:

  1. Adds the IP to rate_limit:blocked:<ip> in Redis with a 24-hour TTL.
  2. Returns 403 immediately.

From that moment, every request from that IP returns 403 instantly — before authentication, before views, before any DB lookup. Your logs get cleaner. Your DB stops processing junk. Real users never notice.

🛡️

The middleware is intentionally aggressive. False positives are rare on REST APIs because real clients do not visit /wp-login.php or send a User-Agent: sqlmap header.

AuthMiddleware

Resolves the session from the cookie and exposes it on the request.

This middleware is the entry point for any non-0-mcp view that wants to know who the user is. It does not enforce authentication — that is BaseResource's job. It just resolves the session and attaches it.

On every request:

  1. Reads the cookie named after COOKIE_ID.
  2. Validates the value against the session-key regex. Malformed → no session.
  3. Looks up sessions:<key> in Redis.
  4. On hit, parses the session JSON and exposes:
request.user            # session['user']
request.account         # session['account']
request.account_id      # session['account']['id']
request.session         # the full dict
request.authenticated   # True/False
  1. Switches the active database connection to the tenant via aset_tenant(account_id).

Resources do their own session lookup inside dispatch — so BaseResource does not depend on this middleware. You only need it for non-0-mcp views (a Django template view, a custom function-based view, an admin redirect) that should know about the session.

🪪

AuthMiddleware does not handle X-Api-Key. API keys are resolved inside the resource's dispatch. Non-resource views authenticated by API key need to call get_api_session(key) themselves.

ExceptionMiddleware

Turns HTTPException into a JSON response.

Whenever any view raises HTTPException(status, detail), this middleware catches it and renders:

{
  "success": false,
  "status": 403,
  "detail": "Admins only"
}

The HTTP status matches the first argument. Anything that is not an HTTPException is left alone — Django's normal error handling kicks in.

Inside resources, BaseResource.dispatch already catches unhandled exceptions and returns sanitized 500s. This middleware exists for the rare case where an HTTPException escapes a non-resource view.

Disabling individual middlewares

Each middleware is independent. Drop the lines you don't need:

0-mcp by Stamatios Stamou Jr — github.com/ssjunior/0-mcp