Skip to main content
mudraid-platform-middleware is how a platform protects its API. It’s middleware for FastAPI and Starlette with two modes:
  • mode="v1" (default) — route and scope enforcement. You add it once, point it at your mudraid_scopes.yaml, and every request is checked for a valid access token and the right scope, locally, before it reaches your handlers.
  • mode="v2" — live decisions for MCP servers. Each protected MCP tools/call is resolved to a canonical action and decided by MudraID’s /decide. See V2 mode.
No decorators. No per-route auth code. No changes to your existing route files.
A few lines, a YAML file in your project root, and the JWKS URL and accepted issuer in your environment — see Configuration.

When to use it

Use the middleware on the platform side — in the API that agents call. If you’re building the agent that makes calls, you want the Python Agent SDK instead.

What it does per request

  1. Matches the method and path against the rules in mudraid_scopes.yaml.
  2. Lets public routes through untouched.
  3. Returns 404 for skip routes and routes with no rule — they’re invisible to agents.
  4. Reads the Authorization: Bearer token.
  5. Verifies the token’s signature, issuer, audience, and timing against MudraID’s published keys — locally, with no per-request callback. Keys are cached and refreshed automatically when MudraID rotates them.
  6. Checks the route’s required scope is present in the token’s scope claim.
  7. Forwards to your handler, or returns a structured {"error_code", "message"} error.
Your handlers only ever run for requests that are authenticated and in scope — once the middleware is actually in the ASGI stack. Adding the package and the YAML does not put it there; add_middleware does, and the quickstart’s verification step is how you confirm it before relying on any of the above.

Install

Requires Python 3.10 or newer. It brings in pyjwt[crypto], cryptography, httpx, pyyaml, and starlette.

Performance

The middleware verifies tokens locally, so the steady-state cost is small. Measured overhead over a bare FastAPI handler with a warm key cache: The scope-gated cost is dominated by the one-time signature verification per request. Fetching MudraID’s keys happens once at startup (and on rotation), not on every call.

V2 mode: live decisions for MCP servers

V2 is for MCP Streamable HTTP servers only. It treats GET/HEAD/OPTIONS as transport and DELETE as MCP session control, and makes no /decide call for them — correct for MCP, wrong for a REST API. Enforce REST routes with V1. V2 is staging-qualified: deploy it behind a version you control and test it end to end before relying on it in production.
On a protected path, V2 strips reserved x-mudraid-* request headers, bounds the request body, rejects JSON-RPC batches, and lets the control methods initialize, ping and tools/list through without a decision. Every tools/call is matched — exactly and case-sensitively — to a canonical action in your platform’s signed action bundle, then sent to MudraID’s /decide along with the caller’s Authorization header. Only an allow reaches your handler. A missing bundle, an unmapped tool, a deny, a timeout or an unreachable authority are all refused. HttpDecideClient is the supported client. It fetches and verifies the signed bundle, keeps it refreshed, makes the authenticated /decide call, and reports adapter heartbeats and acknowledgements to the portal. It needs the MudraID origin for your environment and the adapter token shown once when you register the adapter; both are printed by the portal.
Set protected_paths to your MCP endpoints. It matches whole path segments: /mcp covers /mcp and /mcp/tools, not /mcpfoo. The V2 settings and their errors are on Configuration and Error responses.
HttpDecideClient verifies a decision signature whenever a response carries one, and require_signed_decisions=True refuses unsigned responses. MudraID does not sign decision responses by default, so leave that flag off unless MudraID has confirmed signing for your surface — with it on and signing off, every decision is refused.

Next